Reading a Transaction History: Deltas, Not Summaries
An interface tells you what it thinks a transaction did. The transaction tells you what changed. Those two are usually the same and the gap between them is where most wrong conclusions start, so this page reads a history the slow way: account lists, instruction order, and the balances recorded immediately before and immediately after execution.
- Applies to
- Any confirmed Solana transaction, successful or failed, on any cluster
- Inputs
- The transaction object: account keys, instructions, logs, pre and post balances, fee
- Output
- A per-transaction line stating what moved, which programs ran, and what the fee was
- False-positive mode
- Trusting a parsed summary that mislabels an instruction the parser did not recognise
- Out of scope
- Inferring purpose or ownership from instruction content
Read a Solana transaction history from the transactions themselves: for each signature, compare the account balances recorded immediately before execution with the balances recorded immediately after, note which programs ran and in what order, and record the fee separately from the flow. Summaries produced by an interface are a convenience layer built on those numbers, and they inherit every assumption their author made.
This is not a purist argument. Parsed views are fast and mostly right. The problem is that they fail silently on exactly the transactions worth investigating, which are the unusual ones, and a silent failure in a summary reads as a boring transaction rather than as a gap.
What a transaction actually contains
A confirmed transaction is a small structured object. It carries a list of accounts it may read or write, one or more instructions naming a program and the accounts that program may touch, one or more signatures, a recent blockhash that bounds its validity, and after execution a result object containing the fee, the logs, and arrays of balances from before and after.
The account list is more informative than it looks. Every account a transaction touches must be declared in advance, marked as writable or read-only and as a signer or not. That declaration is a compact statement of what the transaction was permitted to affect, and reading it first tells you the scope of what follows before you decode a single instruction.
| Field | What it tells you | Common misreading |
|---|---|---|
| Account keys | Everything the transaction was allowed to touch, and which of those it could write | Treating every listed account as a participant when many are read-only references |
| Signatures | Which keys authorised it; the first signer normally pays the fee | Assuming the fee payer is the beneficiary of the transaction |
| Instructions | The programs invoked, in order, with their account slices | Reading only the top level and missing everything done through inner calls |
| Pre and post balances | Exact SOL movement per account, in lamports | Forgetting the fee is included in the payer's delta |
| Token balance arrays | Token account balances before and after, with mint and owner | Comparing raw integers across mints with different decimals |
| Log messages | Program invocation order, success or failure, program-emitted detail | Treating log text as a specification when it is whatever a developer chose to print |
| Error field | Whether execution failed and which instruction index failed | Ignoring failures, which removes the most informative transactions from view |
Balance deltas are the ground truth
The pre and post balance arrays are the closest thing to an unarguable record of what a transaction did. They are aligned with the account key list by index, so the SOL movement for account number three is the third entry of the post array minus the third entry of the pre array, in lamports, with one SOL equal to 1,000,000,000 lamports.
Token balances get their own arrays, and these are richer because each entry carries the mint, the owner and the decimals alongside the amount. That means you can compute a token delta without knowing anything about the program that produced it, which is exactly what you want when the program is unfamiliar or when the transfer happened several layers down inside a routing call.
Work in the smallest unit and convert once, at the end, for display. Converting early and comparing rounded values is how two token movements that differ by a rounding step end up recorded as identical, and identical amounts are one of the signals people later use to argue that two accounts are related.
Instructions, inner instructions and logs
The instructions you can see in the signed message are only the outer layer. When a program calls another program, a mechanism the documentation calls cross-program invocation, the network records those calls as inner instructions attached to the index of the outer instruction that caused them. In a routed swap, the outer instruction is usually one call to a router, while the actual token movements live in a nest of inner transfers beneath it.
Logs sit alongside and record the invocation sequence: program invoked, depth, success, consumed compute units. They are useful for reconstructing order and for spotting a program you did not expect in the path. They are not a reliable description of semantics, because their content is whatever the program's authors decided to emit, and it changes between versions without notice.
The practical rule: use logs and instructions to understand structure, use balance deltas to establish effect. When those two disagree, the deltas win, and the disagreement itself is worth a note because it usually means something in the path did more than its name suggests.
Failed transactions are still evidence
Analysts routinely filter failures out and lose the most informative part of a history. A failed transaction landed in a block, paid its fee and produced logs up to the point of failure. It tells you what an operator was attempting, how often, and which error stopped them, and attempts are frequently more revealing than successes.
Two patterns are worth recognising immediately. A run of failures with a slippage error suggests an account repeatedly trying to trade against a market moving faster than its tolerance. A run of failures on account creation or insufficient funds suggests an operator whose funding did not keep up with their scheduling. Neither pattern names anyone, and both change how you read the successes around them.
Why the failure ratio matters to a total
If an account attempted many transactions and only some landed, then any total you compute from successes alone describes outcomes rather than behaviour. State which one you are measuring. A finding that says "this account executed forty swaps" is a different claim from "this account attempted sixty swaps, of which forty landed", and only the second one survives contact with the raw history.
Fees, rent and the arithmetic of noise
A large share of the small SOL movements in any history is not activity at all. Solana charges a base fee of 5,000 lamports per signature, plus an optional priority fee equal to a compute unit price multiplied by a compute unit limit, where the price is denominated in micro-lamports per compute unit. Both are paid by the fee payer whether the transaction succeeds or fails.
The arithmetic below is illustrative and uses protocol constants only. Take a transaction with one signature, a compute unit limit of 200,000 and a compute unit price of 20,000 micro-lamports. The priority component is 200,000 multiplied by 20,000 micro-lamports, which is 4,000,000,000 micro-lamports, or 4,000 lamports. Add the 5,000 lamport base fee and the transaction costs 9,000 lamports, which is 0.000009 SOL.
Across one hundred such transactions that is 900,000 lamports, or 0.0009 SOL, of pure cost appearing as a hundred small debits in the history. Separately, opening a token account requires a rent-exempt deposit, on the order of 0.00204 SOL for a standard token account, which returns to the owner if the account is later closed. Neither of these is flow, and counting them as flow is a routine way to inflate an account's apparent turnover.
Parsed labels are interpretations
Explorers and indexers show helpful strings: "Swap", "Transfer", "Unknown", "Jupiter", "Raydium", "Deposit". Each of those is a decoder's opinion about an instruction, produced by code someone wrote against a program interface at a particular time. When the program updates, or when a new program appears, the decoder falls back to raw data and the interface displays nothing interesting.
Treat every label as a claim with an author. That does not mean discarding them; it means recording the label and the underlying evidence separately, so that a finding rests on "these balances changed in this direction" rather than on "the explorer called it a swap". This distinction is the whole subject of labelling what you find, which extends the same caution from tool labels to the labels you write yourself.
Reading a history as a sequence
Once individual transactions are readable, the history becomes a time series and different questions open up. Group the signatures into sessions by looking for gaps: a burst of activity separated from the next burst by hours of silence is a session boundary, and sessions are a more natural unit of analysis than days.
Within a session, look for the opening move and the closing move. Accounts driven by hand often begin with a small test transaction. Accounts driven by software often begin with an account creation or an approval and end abruptly mid-pattern when a process stopped. Neither observation identifies anyone; both change what the sequence is likely to mean.
Across sessions, watch for changes in rhythm rather than in volume. A change in priority fee settings, a new program appearing in the path, or a shift from one venue to another usually indicates a configuration change by whoever operates the account. That is a real, dateable event, and it is often the most defensible finding a history produces.
Human rhythm and machine rhythm
Software leaves a rhythm. Intervals cluster tightly, sizes repeat or follow a simple rule, instruction sequences are identical transaction after transaction, and the compute budget instruction carries the same values every time. Human activity is lumpy: irregular gaps, round-number amounts, occasional obvious mistakes, and long silences that do not align with anything.
Distinguishing the two changes your reading, but be careful with the conclusion it invites. An automated Solana volume bot operated openly by a project produces the same tight intervals and repeated instruction sequences as any other scheduled process, so rhythm establishes that a process is running and nothing more. Whether that process was disclosed, and by whom, is not recorded anywhere on chain.
The false-positive mode of rhythm reading
Two unrelated operators running the same widely distributed software produce nearly identical rhythms, because the rhythm belongs to the software rather than to the operator. Every conclusion of the form "these accounts share a controller because they behave the same way" has to survive that alternative, and in a market where a handful of tools dominate, it very often does not.
Reconciling a history against a claim
Most of the time you are not reading a history for its own sake. Somebody has said something about an account and you want to know whether the record supports it. Reconciliation is the cleanest use of transaction reading, because the claim supplies the question and the deltas supply a yes, a no, or a specific reason the question cannot be settled.
The procedure is short. Restate the claim as something countable: an amount, a count of transactions, a date boundary, a venue. Pull the signatures that fall inside the relevant window. Compute the quantity directly from deltas rather than from any precomputed total. Then compare, and record the difference along with a note on what could account for it before deciding the claim was wrong.
The usual sources of an honest discrepancy are worth listing, because they explain most gaps without anyone having misled anybody. Fees and rent deposits counted as flow; failed attempts included or excluded; token accounts opened and closed inside the window; wrapped SOL round trips read as deposits; transfers routed through an intermediate account so one movement appears as two; and time zones, where a day boundary in local time cuts a session in half against a UTC reading.
If none of those close the gap, the honest output is still narrow: the amount computed from the record differs from the amount claimed, by this much, over this window, using this definition. That sentence is defensible. Any sentence about why the numbers differ is speculation unless someone off chain explains it.
The per-transaction checklist
- Record the signature in full and the block time in UTC.
- List the programs invoked in order, including inner instructions.
- Compute SOL deltas per account in lamports, noting which account paid the fee.
- Compute token deltas per token account, keeping mint and decimals attached.
- Note the success or failure status and, if failed, the failing instruction index.
- Separate fee and rent from flow before any total is computed.
- Record any parsed label as a label, next to the evidence rather than instead of it.
- Write one sentence describing the net effect, using only what the deltas support.
What a history will not tell you
A complete, correctly read history still leaves the important questions open. It does not say who signed, whether one person or a team held the key, whether the key was compromised, or what agreement sat behind a transfer. It does not distinguish a purchase from a repayment, a gift from a payment, or a mistake from a plan, because those distinctions live entirely off chain.
It is also bounded. Your endpoint holds a window of history, an interface may cap what it displays, and archival access changes what exists to be read. Before writing that an account did something for the first time, or stopped doing something, confirm that the boundary you are describing is the account's boundary and not your query's. The failure modes that follow from ignoring that are collected in where a trail goes cold.
What you are left with is genuinely valuable: a dated, verifiable sequence of effects that anyone can reproduce from the same signatures. That is the raw material every other method on this site consumes, and no amount of clustering, labelling or funding analysis is better than the transaction reading underneath it.
Questions this page gets asked
What is the difference between a transaction and an instruction on Solana?
A transaction is the signed unit that lands in a block. Inside it sits an ordered list of instructions, each naming a program to call, the accounts that call may touch, and a blob of data. One transaction can carry many instructions across several programs, and they either all succeed or the whole transaction fails and none of them take effect.
Why does a transfer show up as two different amounts?
Usually because you are comparing a token amount to a raw amount. Token balances are integers scaled by the mint decimals, so a display value of 1.5 for a six-decimal token is stored as 1500000. Native SOL has nine decimals, meaning one SOL is 1,000,000,000 lamports, and mixing the two scales is a routine source of nonsense totals.
Do failed transactions cost anything?
Yes. A transaction that lands and then fails during execution still pays its fee, because the network did the work of processing it. That is why a history full of failures still shows a steadily draining SOL balance, and why counting only successful transactions understates how much an account was actually attempting.
What are inner instructions?
They are the calls a program makes to other programs while handling your instruction, recorded separately from the instructions you signed. Most real value movement in a swap happens in inner instructions, so a reading that looks only at top-level instructions will see a call to a router and miss every transfer underneath it.
Can a transaction move tokens without an obvious transfer instruction?
It can look that way if you read only the instruction list, because a program may move balances through cross-program calls that appear as inner instructions rather than as a transfer you signed. This is precisely why balance deltas are the reliable reading: they capture the net effect regardless of which layer produced it.
Why do explorers sometimes label a program as unknown?
Because parsing depends on someone having written a decoder for that program. A new or uncommon program shows up as raw data, and the transaction will look empty even when it did something substantial. Unknown means unparsed, not inactive, and the balance deltas will still show what changed.
How do I tell a swap from a plain transfer?
Count the directions. A transfer moves one balance down and another up in the same asset. A swap shows one token account decreasing and a different mint increasing for the same owner within one transaction, usually with a pool or router program in the instruction list and several inner transfers underneath.
Filed under Following a wallet by The Trace Rack Desk. Addresses in the examples are placeholders written as letters rather than base58, so nothing here points at a real account. Behaviour described comes from protocol documentation and from queries the desk can run against public data; arithmetic is labelled as illustrative and describes no real wallet. Scope and refusals are set out in the casework note.