
Agave 4.2: The Migration Checklist
We covered the Agave 4.2 feature set in our release overview. This post covers migration: what breaks and how to verify your integration before the feature gate activates.
Most of these changes return wrong or missing data instead of errors, so they won't show up in your exception logs.
Activation Timeline
When | What | Status |
|---|---|---|
Aug 11, 2026 | Anza recommends 4.2 for mainnet; validator upgrades begin | In progress |
As nodes upgrade | Account-update suppression and Token-2022 | Done |
First epoch boundary after upgrade |
| Done |
Epoch 1020 | Slot times cut from 400ms to 350ms, first of four steps toward 200ms | Done |
Epoch 1024 | Slot times cut from 350ms to 300ms | Scheduled (expected on Aug 28) |
Feature gate, date TBD | Transaction v1 (4,096-byte transactions) | Pending (no announced date, so prepare as if it activates next week) |
Five feature gates, date TBD | Rent reduction, 6,960 lamports/byte stepping down to 696 | Pending |
Agave 4.3, targeted Oct 2026 | Alpenglow consensus | Future release |
Agave 4.2 breaking changes
Transaction v1 fails the whole call
SIMD-0296 and SIMD-0385 raise the Solana transaction size limit to 4,096 bytes through a new transaction format.
The extra room lets developers run bigger on-chain workloads, like ZK proofs or DEX routes with more legs.
The catch: one v1 transaction in a block breaks getBlock for the whole block if the call doesn't set maxSupportedTransactionVersion: 1.
The other transactions don't come back either; the entire call errors. getTransaction and getTransactionsForAddress (with transactionDetails = full) fail the same way on any v1 transaction.
How to fix:
- Upgrade your SDK to a release that decodes v1. Support is landing across clients. If your SDK hasn't shipped transaction v1 support yet, watch its release notes for transaction v1 or SIMD-0385.
- Popular JS clients: @solana/kit 8.0+, @solana/web3.js v3
- Popular Rust crates: solana-rpc-client-api 4.2+, solana-client 4.2+, solana-transaction 4.2+, solana-compute-budget 4.2+, other Anza crates.
- Set
maxSupportedTransactionVersion: 1on every getBlock, getTransaction, and getTransactionsForAddress call (withtransactionDetails = full) - Set
maxSupportedTransactionVersion: 1on WebSockettransactionSubscribecall
{
"jsonrpc": "2.0",
"id": "1",
"method": "getTransactionsForAddress",
"params": [
"Vote111111111111111111111111111111111111111",
{
"transactionDetails": "full",
"sortOrder": "desc",
"filters": {
"status": "succeeded"
},
"maxSupportedTransactionVersion": 1
}
]
}
The parameter declares the highest version your client handles. It is safe to set today and changes nothing about how legacy and v0 transactions come back.
Transaction v1 compute budget failures
A v1 transaction stores its compute limit and priority fee in the transactionConfig object instead of ComputeBudget instructions. Fee dashboards and priority fee estimators that detect fees by matching those instructions read every v1 transaction as paying zero, with no error.
How to fix:
Read the values from the new priorityFee field in the transaction config instead. Note the new priorityFee field states a total fee in lamports (not price per compute unit).
"message": {
"instructions": ["… no ComputeBudget instruction here …"],
"recentBlockhash": "...",
"transactionConfig": {
"computeUnitLimit": 200000,
"heapSize": null,
"loadedAccountsDataSizeLimit": 200000,
"priorityFee": 50000
}
}
Unchanged accounts stop emitting updates
Agave 4.2 only emits account events when an account is actually written. For LaserStream gRPC and WSS account subscriptions, this means roughly 80% fewer events.
How to fix:
- If you match transactions to account updates, stop waiting for an update from every writable account; treat a missing update as "the account did not change". Only the fee payer is guaranteed to update, since it always pays the fee.
- If you use update frequency as a health signal, remove that check for accounts that are often locked but rarely changed. A quiet account is healthy; it stopped emitting because nothing changed.
A new rewardType value
Stake accounts that finish deactivating now receive their final payout under a new rewardType value, DeactivatedStake, in getBlock and blockSubscribe reward arrays. The reward object shape is identical to Agave 4.1, so a parser that only accepts the reward types it already knows will skip DeactivatedStake payouts without an error.
getInflationReward is unaffected; the risk only applies where you parse raw reward arrays yourself.
How to fix:
Add DeactivatedStake to the rewardType values your parser accepts, and log any value you don't recognize instead of dropping the record.
{
"pubkey": "...",
"lamports": 10000000,
"postBalance": 50000000000,
"rewardType": "DeactivatedStake",
"commission": null,
"commissionBps": 500
}
Token-2022: one field removed, parsing expanded
In jsonParsed responses, depositConfidentialTransfer and withdrawConfidentialTransfer lose source and destination in favor of a single account field. The old labels were incorrect; each instruction touches one token account.
The fix is to update Token-2022 parsers to support the new field.
{
"parsed": {
"type": "depositConfidentialTransfer",
"info": {
"account": "6XVfUq9jZQtBfqcm1Rz8fBhViyoyzWiEUAaWnQ9AmXeR",
"mint": "8fJ7bCZo2vZ3vAnyCBQgZuLYuTX1qPnKvsMKotk92B2J",
"amount": 42,
"decimals": 9,
"owner": "F7yLk3s2iZDPBBvSNbXAaKPBnQ9vXqSSSzYUCGeUFhXn"
}
}
}
Permissioned burn, unwrapLamports, confidentialBurn, and batch operations now come back as parsed JSON instead of raw bytes.
{
"parsed": {
"type": "unwrapLamports",
"info": {
"source": "9rr9Xh6PXPKcVqbCB1qxGDWRUJJAdguqcUqVuUqEjqQK",
"destination": "BhU2wDgmvvMNC1vTSU4aG7BvW26MoTMSDL63hcMqziGL",
"amount": "1000000",
"authority": "F7yLk3s2iZDPBBvSNbXAaKPBnQ9vXqSSSzYUCGeUFhXn"
}
}
}
Mints carrying previously unrecognized extensions used to return an empty extensions array; on Agave 4.2 the array is populated.
If your code treats an empty array as "no extensions," expect values to start appearing there.
"extensions": [
{
"extension": "transferFeeConfig",
"state": {
"transferFeeConfigAuthority": "...",
"withdrawWithheldAuthority": "...",
"withheldAmount": 0,
"olderTransferFee": {...},
"newerTransferFee": {...}
}
},
{
"extension": "permissionedBurnConfig",
"state": {
"authority": "3nGhQzXCzoDDvW9pkg8fVLGDrJc23uwFz7qzW26MoTMS"
}
}
]
Slot timing constants are stale
Mainnet runs 350ms slots as of epoch 1020. The next cut, to 300ms, is scheduled for epoch 1024 (expected on Aug 28), with 200ms as the target. Hardcoded 400ms constants in slot-to-time math are off by 12.5% today and will drift further with each step.
How to fix:
Derive timing from block timestamps or make it configurable, and make sure your indexer can keep up when blocks start arriving faster.
Verify you're done
To verify you’re done, run the agent audit and confirm your dependencies.
Run the agent audit
Copy the skill below into your coding agent.
For Claude Code, save it as .claude/skills/agave-42-readiness/SKILL.md.
For other agents, paste it as a task prompt.
---
name: agave-42-readiness
description: Audit this repository for Solana Agave 4.2 breaking changes. Use when asked to check Agave 4.2 readiness, migrate for transaction v1, or audit Solana RPC/streaming integration code.
---
# Agave 4.2 Readiness Audit
Audit the repository for the five Agave 4.2 breaking changes below. For each, locate the relevant code, judge whether it is affected, and report file:line with a verdict of PASS, FAIL, or NEEDS REVIEW. Do not modify code unless asked to fix findings.
## Check 1: Transaction v1 opt-in (highest priority)
Find every `getBlock`, `getTransaction`, and `getTransactionsForAddress` (with `transactionDetails = full`) call. Search all languages, raw JSON-RPC request bodies (`"method": "getBlock"`), and SDK wrappers (`connection.getBlock`, `connection.getParsedTransaction`, `rpc_client.get_block`, `client.GetTransaction`, and similar).
- FAIL if `maxSupportedTransactionVersion` is absent or set to 0. Once the v1 feature gate activates, these calls fail on v1 transactions with JSON-RPC error code -32015: `"Transaction version (1) is not supported by the requesting client. Please use \"maxSupportedTransactionVersion\" in your request."` Also grep for `-32015` in logs and error handlers; hits mean the project has already been failing on versioned transactions.
- Also FAIL if the project deserializes raw transaction bytes (custom indexer, signer, relayer) and the decoder does not handle the v1 layout: version byte 0x81 (decimal 129, vs 0x80 for v0), signatures at the END of the transaction instead of the front, compute budget carried in a header config mask instead of instructions.
- Correct fix order: upgrade the Solana SDK to a v1-capable version (JS: @solana/kit 8.0+ or @solana/web3.js v3), then set `maxSupportedTransactionVersion: 1`. Flag if the parameter is set but the installed SDK version predates v1 support.
- Also FAIL if priority fee or compute budget extraction scans for ComputeBudget program instructions (`ComputeBudget111111111111111111111111111111`, `setComputeUnitPrice`, `setComputeUnitLimit`). A v1 transaction contains no such instructions; its values live in a `transactionConfig` object in the message. Scanning code reads every v1 transaction as paying zero priority fee.
- Units trap, FAIL even when the code reads `transactionConfig`: legacy/v0 `setComputeUnitPrice` is **micro-lamports per compute unit**; v1 `priorityFee` is a **total in lamports**. Code that ports the old `price × computeUnitLimit ÷ 1e6` math onto the new field misreports fees. `priorityFee` needs no multiplication. Null fields mean the sender did not set them.
Example v1 message:
```
"message": {
"instructions": ["… no ComputeBudget instruction here …"],
"recentBlockhash": "...",
"transactionConfig": {
"computeUnitLimit": 200000,
"heapSize": null,
"loadedAccountsDataSizeLimit": 200000,
"priorityFee": 50000
}
}
```
`"priorityFee": 50000` = this transaction pays 50,000 lamports total. Legacy and v0 messages omit `transactionConfig` entirely, so its presence (or `"version": 1` on the enclosing transaction) identifies v1.
## Check 2: rewardType parsed as a closed enum
Find code reading `rewardType` from `getBlock`, `blockSubscribe`, or Geyser/gRPC reward arrays. Search for the existing values too (`"Fee"`, `"Rent"`, `"Staking"`, `"Voting"` in JSON paths; `RewardType::` in Rust; reward-type enums in generated gRPC code).
- 4.2 adds the value `DeactivatedStake` — same capitalized casing as the existing JSON values (`"Staking"`, not `"staking"`). A reward entry looks like:
```
{
"pubkey": "...",
"lamports": 10000000,
"postBalance": 50000000000,
"rewardType": "DeactivatedStake",
"commission": null
}
```
- FAIL if unknown values throw, or fall into a match/switch arm or if/else chain that drops the record without logging: a Rust `match` without a logging wildcard arm, a TS `switch` whose `default` is silent, a lookup table where a missing key skips the entry. Closed parsers lose every deactivating stake account's final payout with no error.
- FAIL if a deserialization enum (serde, protobuf mapping, Zod/io-ts schema) rejects unknown reward-type strings; the whole reward array or block record errors out, not just one entry.
- PASS requires unknown values to be preserved or logged. If the project aggregates staking yield by filtering `rewardType == "Staking"`, add a NEEDS REVIEW noting the team must decide whether `DeactivatedStake` payouts belong in totals; they are final payouts, not recurring yield.
## Check 3: Transaction-to-account-update matching
Find logic that correlates transactions with account update notifications. Search for `accountSubscribe`, Yellowstone/LaserStream `SubscribeRequest` account filters, and code that builds a pending set of a transaction's writable accounts and waits to check them off as updates arrive.
- FAIL if it assumes every writable account in a transaction produces an update. On 4.2, an account that was write-locked but not modified emits nothing; logic awaiting the full set hangs forever. Typical patterns: a countdown/completion latch over `message.accountKeys` writable entries, a timeout that treats a missing update as an error, a reconciler that re-fetches "late" accounts. The fee payer is the only account guaranteed to update.
- The correct interpretation of a missing update is "this account did not change"; its pre-transaction state is still current.
- Also FAIL liveness or health checks that treat update frequency as a signal, and flag alert thresholds calibrated to pre-4.2 volume; account-subscription event counts drop roughly 80% on 4.2.
## Check 4: Token-2022 jsonParsed changes
Find parsers reading Token-2022 `jsonParsed` output: instruction types `depositConfidentialTransfer` and `withdrawConfidentialTransfer`, and mint-account `extensions` arrays.
- FAIL if confidential-transfer parsers read `source` or `destination`; 4.2 replaces both with a single `account` field. New shape:
```
"info": {
"account": "...", // was source + destination
"mint": "...",
"amount": 42,
"decimals": 9,
"owner": "..." // multisig: "multisigOwner" + "signers" array instead
}
```
- New instruction types now come back parsed instead of raw: `unwrapLamports`, `confidentialBurn`, permissioned burn, and batch operations. FAIL if code assumes these arrive as raw base64/base58 data, and flag `unwrapLamports` parsers that require `info.amount` or treat it as a number: it is a **string** and is **omitted entirely** when the instruction unwraps the full balance.
- Extensions array: on 4.1, one unrecognized extension type on a mint made the node return `"extensions": []`, hiding every extension including known ones. On 4.2 the full array returns, each entry as `{"extension": "<name>", "state": {...}}`, with still-undecodable ones as `{"extension": "unparseableExtension"}`. FAIL if code treats an empty array as proof of no extensions, or throws on extension names it does not recognize (`scaledUiAmountConfig`, `pausableConfig`, `permissionedBurnConfig`, `unparseableExtension`, and future values).
## Check 5: Slot timing and streaming dependencies
- FAIL on hardcoded slot-duration constants: `400`, `0.4`, `400_000` microseconds, `MS_PER_SLOT`, `SLOT_DURATION`, `DEFAULT_MS_PER_SLOT`, or slot-to-time math like `slots * 400`. Mainnet is 350ms, cutting to 300ms on Aug 28, 2026, stepping toward 200ms. Constants inherited from an SDK count as hardcoded. PASS requires timing derived from block timestamps (e.g. `getBlockTime` deltas over a few thousand slots) or a config value with a documented update path.
- Also flag capacity assumptions keyed to block rate: batch sizes, poll intervals, and queue depths sized for 400ms blocks fall behind as slots shorten.
- In Cargo.toml/Cargo.lock: FAIL if `yellowstone-grpc-client` < 13.3.0 or `yellowstone-grpc-proto` < 12.6.0 (first proto carrying transaction v1). Recommend client 13.3.0, proto 12.6.0, both declared.
- For Go gRPC clients: flag if generated protos predate transaction v1; regenerate from latest Yellowstone protos and solana-storage-proto.
- If the project uses the Helius LaserStream SDK (package names `helius-laserstream`, `laserstream` in dependencies): FAIL if below JS 0.8.4, Rust 0.6.3, or Go 0.2.0 (first releases with the v1-capable proto).
## Report format
Output a table: check, verdict, file:line references, one-line remediation. End with an overall verdict (READY / NOT READY) and the ordered fix list. Full migration guide: https://www.helius.dev/blog/agave-4-2-migration-checklistConfirm stream dependencies
Rust: yellowstone-grpc-client 13.3.0 and yellowstone-grpc-proto 12.6.0, both declared, then cargo update.
Go: regenerate from the latest Yellowstone protos and solana-storage-proto.
LaserStream SDK: JS 0.8.4, Rust 0.6.3, and Go 0.2.0 or later include the v1-capable proto. Your subscriptions and filters need no changes on any path.
Questions
If your integration behaves differently on 4.2 and this post doesn't explain it, reach out in Discord or via support. For the feature-level breakdown, read the 4.2 overview.
Related Articles
Subscribe to Helius
Stay up-to-date with the latest in Solana development and receive updates when we post


