Reconstructing Time and Ownership
Accurate token movement and cost basis depend on two contexts: when the transaction happened and who owned the token accounts involved. Early Solana history sometimes lacks both. This guide explains when timestamps and ownership became reliable and how to rebuild them when they are missing.
Abstract
If you analyze Solana’s early mainnet history, you’ll run into two recurring data gaps:
- Missing timestamps (blockTime) in blocks before late September 2020, and during the transition period.
- Missing token-account ownership fields in preTokenBalances and postTokenBalances until well into 2022.
These gaps can obscure token flows, complicate cost basis calculations, and weaken historical transaction analysis. Both issues are solvable once you know when the network began persisting these fields, and how to infer them when they’re absent. This article explains:
- When these fields became consistently recorded.
- Why the gaps occurred in the first place.
- How to reconstruct accurate timing and ownership context when data is missing.
Understanding preTokenBalances and postTokenBalances
When you fetch a transaction from Solana using getTransaction, the response includes two key arrays: preTokenBalances and postTokenBalances.
- preTokenBalances lists each token account’s balance before the transaction executes.
- postTokenBalances lists the balances after execution.
Each entry contains:
- The mint address of the token (e.g., USDC’s mint address is EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v).
- The accountIndex, linking to the token account’s public key in the transaction’s accountKeys array.
- The owner of that token account (under modern conditions).
- The balance in both raw integer and uiAmount form.
Figure 1: Pre/Post balance for Account 2 is used to calculate the net change. Index 2 (zero-based) of the accountKeys array tells us the Associated Token Account for the owner where the token is stored.
By pairing up matching accounts between the pre- and post- arrays, you can calculate net balance changes directly, without parsing all instructions. Under normal, modern conditions, the presence of the owner field makes this straightforward. You can immediately associate a balance change with a specific wallet, even if the token account is an Associated Token Account (ATA) or a program-owned account.
Why these fields are missing in older data
Missing blockTime
Solana computes block time from stake-weighted vote timestamps that validators include in vote transactions. The RPC method getBlockTime returns that estimate, but for older history the timestamp simply wasn’t written into the long-term block store. Those timestamps were added to long-term storage around late Sept 2020, so blocks before then (and some around the transition) can be null. Figure 2 below shows a Solscan screenshot of one such transaction.
Figure 2: Example of a Solana transaction with no block time. All we know is that it took place at block 9530328.
Software gets transaction data from the blockchain by calling the getTransaction RPC method. Figures 3 and 4 show a snippet from two transactions retrieved this way. One with the blockTime and one without.
Figure 3: Transaction missing blockTime
Figure 4: Transaction with blockTime
Sadly, you’ll see a run of early mainnet slots where getTransaction → result.blockTime (or getBlockTime(slot)) is not available even though the transaction and block records exist.
Missing token-account owner on pre/post balances
Token balance diffs in getTransaction are exposed via meta.preTokenBalances and meta.postTokenBalances. Today, each balance item includes mint, accountIndex, uiTokenAmount, and crucially the token account owner. But historically the owner (and later programId) fields were not captured in the archived metadata for older transactions.
The Solana team recorded the change to start storing the owner field on Oct 13, 2021 (landed on master here: https://github.com/solana-labs/solana/pull/20642). Rollout to mainnet-beta lagged the code landing, so there are plenty of transactions after this date still missing owner fields. Testing at NODE40 revealed that you are pretty safe after Mar 1, 2022. For transactions before those cutovers, the owner can be undefined in the balance objects even when the token account obviously has an owner on-chain.
This means there’s a long window where timestamps are mostly, but not always present (post-Oct 2020) but token balance owner is not (pre-mid/late Q4 2021 / early 2022 rollout). There’s also an earlier window where both can be missing (pre-Oct 2020 for time; plus missing/partial token balance metadata depending on when recording was enabled).
Practical reconstruction strategies
At NODE40, when we encounter a transaction with either missing timestamps or missing owner fields, we must carefully consider how to assign both attributes. If we had an unlimited budget, I would replay the entire blockchain and just backfill every missing data point, but that would be extremely expensive in both CPU, time, and storage. This path was not feasible, so I developed a series of algorithms to calculate the timestamps, owner fields, and net balance changes per token in real-time as-needed for each Solana account we manage, which was around a half million unique accounts participating in many millions of transactions at the writing of this article. Since the process would be real-time, speed was the most important factor after accuracy.
Recovering timing when blockTime is null
While I cannot provide the exact details of our proprietary algorithm, I can tell you it uses slot interpolation, as you might expect, and the results are tested to be reasonable, defensible, and repeatable. For speed, the algorithm is highly threaded and uses two caching layers for reduced computations.
Re-deriving token-account owner when it’s missing in tokenBalances
Calculating net balance changes with an ownership field is trivial but when the field is missing it can get complex quickly. The situation is made even more difficult when the uiAmount field is also missing (very common), which I will explain later.
Since the algorithm is executed in real-time, it was important to reduce calls to RPC. First, RPC calls consume time and second, RPC calls cost money.
One option is to find all pubkeys in the pre/post balance arrays and then call getAccountInfo from RPC. This will tell you which account owns the pubkey. Two problems with this approach:
- It is costly in dollars, time, and compute. You must make at least 1 call per pubkey and for transactions involving many accounts, the cost adds up fast.
- The call to getAccountInfo will tell you which account owns the pubkey now, not then. Accounts can be transferred, so you can miss a valid attribution.
Another option is to check ATA derivation. ATA, or Associated Token Account is a Program Derived Account (PDA) that is created as a combination of the wallet account plus the mint address of the token it will store. In Solana non-SOL tokens are stored in separate accounts that are owned by a primary account. This is what makes the owner field so important. Without the owner field, the transaction just shows transfers/mint/burns to and from token accounts, not the owner account. The following two screenshots show how Solscan presents a transaction with and without the owner field.
Figure 5: [Solana Screenshot] Transaction taking place on Sept 25, 2021 with no owner field. It is unknown who owns which USDC token account.
Figure 6: [Solana Screenshot] Transaction taking place on Aug 15, 2025 with owner field. It is clear who owns which token account.
If you choose to do this, it’s good practice to derive ATAs for both legacy tokens and the token 2022 standard to be absolutely sure the token account is not an ATA.
Non-ATA Token Accounts
Even if a token account is not an ATA (i.e., a PDA of the legacy or token 2022 standard), it may still be owned by the account for which you are calculating the balance changes.
In Solana, ATAs are merely a convenient place to store your tokens, but there is no protocol rule that prevents you from creating a random account and initializing it to store a particular token. These accounts are the worst and cause the most trouble. These are the accounts that took me the longest to crack, but it is possible to identify them. One way to identify them is to look for transfers from them inside the transactions where the authority on the transfer is the primary account. When such transfers are observed, it is safe to assume that the source account is a token account under the control of the primary. What happens when a different account sends value to a non-ATA account? If you process signatures from the primary account, this transaction will not be included and you’ll miss it altogether. How do we do it at NODE40? I can’t share that, but we do it and it works quite well.
Putting it together: reliable token movement despite gaps
Reliable token movement tracking in early Solana requires normalizing both timing and ownership before calculating net changes. Use blockTime when available, and clearly mark any reconstructed timestamps. For ownership, rely on the owner field when present, and infer it for older transactions using proven resolution methods. Once these contexts are established, compute movement from pre- and post-balance deltas rather than relying solely on instruction parsing.
What this means for cost basis
These reconstruction steps directly impact the accuracy of financial reporting, particularly for cost basis calculations. If you’re building financial statements from early Solana:
- Treat reconstructed blockTime as estimated and keep original slot numbers alongside it.
- Treat reconstructed token-account owner as verified once confirmed via ATA derivation and authority history, even if it’s absent in tokenBalances.
Prefer balance-delta accounting (pre vs post) to classify deposits/withdrawals, and only fall back to instruction semantics when balance arrays are absent. This mirrors Solana’s own guidance for production systems and is the only safe way to handle early transactions.