How to Query Historical Account Balance for Rebasing Liquid Staking Tokens
(This article was first published on LinkedIn on September 24, 2025)
Liquid staking tokens come in different designs. Some, known as rebasing tokens, automatically adjust the balance displayed in your wallet without any incoming transactions. In practice, your token count rises daily as staking rewards accrue, but under the hood, what you really hold is a fixed number of shares.
In this post, we examine how rebasing works, the complexities it creates in accounting, and how to query historical balances using archival blockchain data.
What are Rebasing Tokens?
“Rebasing” liquid staking tokens automatically adjusts their balance in users’ wallets to reflect staking rewards. Examples of these tokens include AAVE’s sUSDC, Ankr’s aDOTb, Ampleforth’s AMPL, Lido’s stETH, Origin Protocol’s OETH, Olympus DAO’s sOHM, and Klima DAO’s sKLIMA.
Shares vs. Balances
When depositing assets into a liquid staking protocol that employs rebasing tokens, users receive a fixed number of “shares”. A user’s wallet displays the token balance returned by a smart contract query. But the smart contract query does not return the number of shares. Rather, it returns the balance calculated as:
[Total ETH in the Pool] * [User Shares] / [Total Shares],
…where:
[Total ETH in the Pool] = [Staked by the Pool ETH] + [Accrued by the Pool ETH Rewards].
As a result, the user’s balance of tokens in the wallets increases daily without incoming transactions.
Accounting Implications
For accounting purposes, the choice between using shares or token natural units depends on the token. Shares are typically static unless a transaction occurs, while token natural units may change even without transactions. The shares variable is typically private, but some contracts allow external access to it for accounting purposes. For instance, Lido’s stETH (deployed version with an address 0x17144556fd3424EDC8Fc8A4C940B2D04936d17eb) exposes both sharesOf() and balanceOf(), whereas sKLIMA exposes only balanceOf().
Historical Balance Queries
This can be illustrated using the sKLIMA token on Polygon Network (Contract 0xb0c22d8d350c67420f06f48936654f567c73e8c8).
Displayed using the following implemented in the contract read-only method balanceOf (0x70a08231). You can view it in the user interface here.
The main issue we see in practice is that querying balanceOf() returns the current state of the account, and the user cannot query the historical balance by default. But accountants need balances for a specific time in the past (month-end, or immediately prior to a transaction). Is there a way to obtain this data? Yes, we can do it if we get a little bit more technical. All we need is a block height, access to an archive node, and a preferred code execution environment.
Block height is the sequential number assigned to the most recently mined block included in the blockchain at a specific date and time. For example, as of the date and time when this post was written, Polygon included approximately 77 million blocks, each with a timestamp.
Knowing the block height at a specific point in time in the past, we can query an archival node with a specified block height to return the same result that would be returned by the method if we were to send this request at the desired point in time in the past.
If the node you’re using is not archival, calls for old blocks can fail or return incorrect/unavailable data. Hence, we need a blockchain node provider that provides access to a full archival node (which might not exist for less commonly known blockchains).
Chainstack is the only provider offering access to archive node on Polygon network that I was able to find. Alternatively, you can set up your own node to obtain data directly, allowing you to avoid relying on SOC reports from external providers and ensure the integration of node controls with your entity’s control environment.
So, how do we determine the block height that should be used in a query of the historical account balance?
There are two ways:
The block height for the datetime associated with a specific on-chain transaction can be found in the details of the blockchain transaction.
Locating the block height for the end of a specific reporting period is more challenging. But for blockchains available in Google Big Query, we can get the list of block heights for each month-end using this simple query:
FORMAT_TIMESTAMP(”%Y-%m”, timestamp) AS month,
MAX(number) AS latest_block_number,
MAX(timestamp) AS latest_block_timestamp
FROM bigquery-public-data.crypto_polygon.blocks
GROUP BY month
ORDER BY month;Great. Next question: how do we query the archive node to receive the account balance at a specified block height?
We will use the Google Collab environment to run the code and populate the two code cells as follows:
Cell 1:
!pip install web3Cell 2:
from web3 import Web3
contractAddress = “0xb0C22d8D350C67420f06F48936654f567C73E8C8”
holder = “0x89C825392C739c355Fee242cfAA85C7fcD90f419”
block_number = 76621446
abi = [
{
“inputs”: [{”internalType”: “address”, “name”: “who”, “type”: “address”}],
“name”: “balanceOf”,
“outputs”: [{”internalType”: “uint256”, “name”: “”, “type”: “uint256”}],
“stateMutability”: “view”,
“type”: “function”,
}
]
# Use your Polygon RPC provider URL
POLYGON_RPC_URL = “https://polygon-mainnet.core.chainstack.com/xxxxxxxxx”
w3 = Web3(Web3.HTTPProvider(POLYGON_RPC_URL))
# Sanity check
print(”Connected:”, w3.is_connected())
contract = w3.eth.contract(
address=Web3.to_checksum_address(contractAddress),
abi=abi
)
bal = contract.functions.balanceOf(holder).call(block_identifier=block_number)
print(”Balance at block”, block_number, “:”, bal)Once we run Cell 1 and Cell 2, we receive a message:
Let’s try to run the same code with the older block (ID 56640046). The code returned the following response, indicating a successful request:
Connected: True
Balance at block 56640046: 29464021721To validate the accuracy of the returned data, we can verify the smart contract state using proof. Leo Zhang discussed how to do this in an excellent article Verify Ethereum Smart Contract State with Proof.






