Solana stake accounts are on-chain accounts owned by the Stake program (Stake11111111111111111111111111111111111111) whose data encodes a Meta section (rent-exempt reserve, authorized staker and withdrawer, lockup) and a Stake state that is one of Uninitialized, Initialized, Stake (delegation), or RewardsPool. You can discover stake accounts with getProgramAccounts scoped to the Stake program id, fetch a single account with getAccountInfo using jsonParsed to read the delegation fields, and check the cluster minimum delegation with getStakeMinimumDelegation. Delegation activates over a warmup ramp that can span multiple epochs, and deactivation sets a deactivation epoch after which the account cools down before withdrawal. This article shows how to read those fields over JSON-RPC, compute whether a stake account is fully active or withdrawable, and avoid common pitfalls such as unfiltered getProgramAccounts scans and rate-limit errors.
Stake Program Account Model and State Machine
A Solana stake account is an on-chain account owned by the Stake program, whose program id is Stake11111111111111111111111111111111111111. The account data encodes two logical parts: a Meta section containing the rent-exempt reserve, the authorized staker and withdrawer pubkeys, and an optional lockup, and a Stake state that is one of Uninitialized, Initialized, Stake (delegation), or RewardsPool. The Stake state is the part that carries delegation information such as the voter pubkey, delegated lamports, activation epoch, and deactivation epoch.
The state machine matters because the same account can move from Initialized to Stake when it is delegated, and from Stake back toward withdrawable when it is deactivated. Reading the account over JSON-RPC gives you a snapshot of that state at a specific commitment level. For background on how commitment affects what you see, see Solana commitment levels and transaction confirmation.
The binary layout and the jsonParsed shape are defined by the Stake program and the RPC node's parser version. That means the exact field names and nesting you see can vary by client and runtime, so treat the parsed output as documented behavior that may change across upgrades rather than a frozen schema.
- Owner: Stake11111111111111111111111111111111111111
- Meta: rent-exempt reserve, authorized staker, authorized withdrawer, lockup
- Stake state: Uninitialized, Initialized, Stake (delegation), RewardsPool
- Delegation fields: voter pubkey, stake lamports, activationEpoch, deactivationEpoch
Discovering Stake Accounts with getProgramAccounts
To find stake accounts, call getProgramAccounts with the Stake program id as the program address. Because the Stake program owns many accounts, an unfiltered call can return a very large result set and is expensive for the node. The Solana getProgramAccounts reference documents filters such as dataSize and memcmp that let you narrow the scan. Use dataSize to target the size of a stake account and memcmp to match bytes at a known offset when you need a specific field.
In practice, production code should filter narrowly or use an indexer rather than scanning every stake account. If you must page a large result set, request a bounded range and continue from the last seen account. The pagination patterns in Solana getSignaturesForAddress pagination are a useful mental model for cursor-style reads, even though the method differs.
Provider behavior varies: some endpoints permit large getProgramAccounts scans and some do not. If your request is rejected or throttled, that is documented / varies by provider, and you should reduce the filter scope or switch to an indexed data source.
- Scope the call to the Stake program id
- Use dataSize to match stake account size
- Use memcmp to match a field at a known offset
- Page large results and avoid unbounded scans
Fetching a Single Stake Account with getAccountInfo
For a known stake account address, getAccountInfo returns the account data. Requesting jsonParsed encoding asks the node to decode the Stake program layout into named fields, which is the fastest way to read the delegation struct. The Solana getAccountInfo reference documents the jsonParsed stake account layout, including the parsed delegation fields.
When jsonParsed is unavailable or you need byte-level control, request base64 and decode the binary layout yourself. The base64 path is more stable across parser changes but requires you to track the Stake program layout. For a broader treatment of account reads, rent, and token accounts, see Solana getAccountInfo, rent and token accounts.
The parsed delegation typically exposes the voter pubkey, the delegated stake in lamports, the activation epoch, and the deactivation epoch. Compare those fields against the current epoch to reason about warmup and cooldown.
- Use jsonParsed for named delegation fields
- Use base64 when you need byte-level control
- Read voter, stake lamports, activationEpoch, deactivationEpoch
- Compare epochs to determine active or cooling state
Reading the Cluster Minimum Delegation with getStakeMinimumDelegation
getStakeMinimumDelegation returns the cluster's current minimum delegation in lamports. The Solana getStakeMinimumDelegation reference documents that the return value is the minimum delegation lamports. This value gates whether a new stake account will actually activate: if you delegate less than the minimum, the delegation may not become active.
The minimum is a chain parameter, so it is documented / varies by cluster and upgrade. Do not hard-code it. Read it at runtime and compare it against the amount you intend to delegate before submitting a delegation transaction.
If you are building a staking flow, call getStakeMinimumDelegation first, then validate the user's amount against it. This avoids a confusing state where the account is Initialized but never becomes Stake.
- Returns minimum delegation lamports
- Chain parameter: varies by cluster and upgrade
- Validate delegation amount before submitting
- Prevents Initialized-but-never-active accounts
Activation Warmup and Epoch Timing
Delegation does not become fully active immediately. It goes through an activation (warmup) ramp that can span multiple epochs. The activationEpoch field marks the epoch when the delegation began activating, and the stake becomes fully active according to the stake activation schedule. To compute when a delegation becomes fully active, read the current epoch with getEpochInfo and compare it against activationEpoch.
Cluster epoch timing is not a wall-clock constant. Epoch duration depends on slot timing and cluster configuration, so you should reason in epochs rather than seconds. If you need a wall-clock estimate, derive it from the current epoch's start time and the observed slot rate, and treat it as an estimate.
A practical check is: if the current epoch is greater than activationEpoch plus the warmup span, the delegation is fully active. The exact warmup span is defined by the stake program and can change, so verify against the cluster you are querying.
- activationEpoch marks the start of warmup
- Warmup can span multiple epochs
- Use getEpochInfo for the current epoch
- Epoch duration is not a fixed wall-clock constant
Deactivation Cooldown and Withdrawability
Deactivation sets a deactivation epoch. After that, the account cools down over subsequent epochs before the SOL can be withdrawn. To detect 'deactivating but not yet withdrawable', compare deactivationEpoch against the current epoch: if the current epoch is less than or equal to deactivationEpoch, the account is still cooling down.
Once the cooldown completes, the stake is no longer delegated and the withdrawer can move the lamports. The withdraw path is a transaction against the Stake program, not a JSON-RPC read, but the read tells you when it is safe to attempt it.
If you are monitoring many accounts, track deactivationEpoch and current epoch together. A simple rule is: withdrawable when current epoch is greater than deactivationEpoch plus the cooldown span. As with warmup, verify the cooldown span against the cluster.
- deactivationEpoch marks the start of cooldown
- Cooldown spans subsequent epochs
- Withdrawable when current epoch exceeds deactivationEpoch plus cooldown
- Read state before attempting a withdraw transaction
Runnable Node.js Example: Minimum Delegation, Parsed Account, and Program Scan
The following Node.js example uses @solana/web3.js to call getStakeMinimumDelegation, fetch a known stake account with getAccountInfo jsonParsed, call getProgramAccounts on the Stake program with a dataSize filter, and print whether each account is activating or cooling down. Replace the RPC endpoint and the sample stake account address with your own values.
The example is intentionally small so you can adapt it. It prints the minimum delegation, the parsed delegation fields, a count of stake accounts, and a per-account activation or cooldown status based on the current epoch.
const { Connection, PublicKey } = require('@solana/web3.js');
const RPC_ENDPOINT = process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com';
const STAKE_PROGRAM_ID = new PublicKey('Stake11111111111111111111111111111111111111');
const SAMPLE_STAKE_ACCOUNT = new PublicKey('YOUR_STAKE_ACCOUNT_ADDRESS');
async function main() {
const connection = new Connection(RPC_ENDPOINT, 'confirmed');
// 1. Minimum delegation
const minDelegation = await connection.getStakeMinimumDelegation();
console.log('Minimum delegation (lamports):', minDelegation);
// 2. Parsed stake account
const accountInfo = await connection.getParsedAccountInfo(SAMPLE_STAKE_ACCOUNT);
console.log('Parsed account:', JSON.stringify(accountInfo.value?.data, null, 2));
// 3. Program accounts with dataSize filter
const accounts = await connection.getProgramAccounts(STAKE_PROGRAM_ID, {
filters: [{ dataSize: 200 }],
});
console.log('Stake accounts found:', accounts.length);
// 4. Activation / cooldown status
const epochInfo = await connection.getEpochInfo();
const currentEpoch = epochInfo.epoch;
for (const { pubkey, account } of accounts.slice(0, 10)) {
const parsed = account.data;
const delegation = parsed?.parsed?.info?.stake?.delegation;
if (!delegation) continue;
const activationEpoch = delegation.activationEpoch;
const deactivationEpoch = delegation.deactivationEpoch;
const activating = currentEpoch <= activationEpoch;
const coolingDown = deactivationEpoch !== '18446744073709551615' && currentEpoch <= deactivationEpoch;
console.log(pubkey.toBase58(), { currentEpoch, activationEpoch, deactivationEpoch, activating, coolingDown });
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});Reproducible Results Table for Stake Account Reads
Use the table below to record what your own endpoint returns for each stake account. Fill it in by running the example above and copying the values. This keeps your observations separate from documented behavior and from provider-specific results.
Because the parsed shape and the minimum delegation can vary by cluster and upgrade, the table is the honest way to capture your environment. Do not assume the values you see on one cluster apply to another.
- Address: the stake account pubkey
- State: Uninitialized, Initialized, Stake, or RewardsPool
- Voter: the delegated voter pubkey
- Delegated lamports: the stake amount
- Activation epoch: when warmup began
- Deactivation epoch: when cooldown began
- Current epoch: from getEpochInfo
- Fully active?: current epoch beyond warmup
- Withdrawable?: current epoch beyond cooldown
Limitations, Provider Behavior, and Tradeoffs
The binary layout and jsonParsed shape are defined by the Stake program and the RPC node's parser version. This is documented / varies by client and runtime, so a field that appears in one client may be named differently or absent in another. Always validate the shape before relying on it in production.
getProgramAccounts with a large result set is heavy and often a rate-limit or 429 trigger. Production code should filter narrowly, page results, or use an indexer. If you need managed RPC capacity, review RPC pricing and the API service options.
Cluster epoch timing is not a wall-clock constant, so any estimate of when a delegation becomes fully active or withdrawable should be expressed in epochs and re-checked against the cluster. For endpoint selection and network details, see Solana networks.
- Parsed shape varies by client and runtime
- Large getProgramAccounts scans trigger rate limits
- Epoch timing is not a fixed wall-clock constant
- Prefer narrow filters or an indexer for production
Troubleshooting Common Stake Account Read Failures
If getProgramAccounts returns a 429 or times out, reduce the filter scope, add a dataSize filter, or switch to an indexed source. If jsonParsed returns an unexpected shape, fall back to base64 and decode the Stake program layout yourself. If getStakeMinimumDelegation is unavailable on your endpoint, check the provider's method support, because availability is documented / varies by provider.
If a delegation never becomes active, compare the delegated amount against getStakeMinimumDelegation. If an account appears deactivating but not withdrawable, compare deactivationEpoch against the current epoch from getEpochInfo. For subscription-based reads and encoding choices, see Solana accountSubscribe encoding.
When in doubt, re-read the account at a higher commitment level and confirm the epoch. A stale snapshot is a common cause of confusing state.
- 429 or timeout: narrow the filter or use an indexer
- Unexpected parsed shape: fall back to base64
- Method unavailable: check provider support
- Never active: compare against minimum delegation
- Not withdrawable: compare deactivationEpoch to current epoch
Next Steps for Stake Account Monitoring
Once you can read a single stake account, extend the pattern to monitor many accounts over time. Track activationEpoch and deactivationEpoch, and alert when an account crosses from activating to fully active or from cooling down to withdrawable. Use Solana API guide (RPC Assistant) for method-level context and the OnFinality Learn hub for related guides.
For production workloads, choose an endpoint that supports the methods you need and the scan sizes you require. Review Solana networks and RPC pricing to match capacity to your read patterns.
Finally, keep the results table from this article up to date for your cluster. It is the most reliable way to separate documented behavior from provider-specific behavior and from your own measurements.
- Monitor activationEpoch and deactivationEpoch over time
- Alert on state transitions
- Choose an endpoint that supports your scan sizes
- Keep a per-cluster results table