
Agave 4.2:迁移检查清单
我们已在版本概览中介绍 Agave 4.2 的功能集。本文聚焦迁移:哪些变更会破坏现有功能,以及如何在功能门激活前验证你的集成。
这些变更大多会返回错误或缺失的数据,而不是报错,因此不会出现在你的异常日志中。
激活时间线
| 时间 | 事项 | 状态 |
| 2026 年 8 月 11 日 | Anza 建议主网采用 4.2;验证者开始升级 | 进行中 |
| 随节点升级 | 账户更新抑制和 Token-2022 jsonParsed 变更生效 | 已完成 |
| 升级后的第一个 epoch 边界 | 奖励中出现 DeactivatedStake rewardType 值 | 已完成 |
| Epoch 1020 | slot 时间从 400ms 缩短至 350ms,这是迈向 200ms 的四个步骤中的第一步 | 已完成 |
| Epoch 1024 | slot 时间从 350ms 缩短至 300ms | 已计划(预计 8 月 28 日) |
| 功能门,日期待定 | Transaction v1(4,096 字节 transaction) | 待定(尚未公布日期,请按下周就会激活来准备) |
| 五个功能门,日期待定 | 租金降低,从 6,960 lamports/字节逐步降至 696 | 待定 |
| Agave 4.3,目标为 2026 年 10 月 | Alpenglow 共识 | 未来版本 |
Agave 4.2 重大变更
Transaction v1 会导致整个调用失败
SIMD-0296 和 SIMD-0385 通过新的 transaction 格式,将 Solana transaction 大小上限提升至 4,096 字节。
额外空间让开发者能够运行更大的链上工作负载,例如 ZK 证明或包含更多路径段的 DEX 路由。
需要注意的是:如果调用未设置 maxSupportedTransactionVersion: 1,区块中只要有一笔 v1 transaction,就会导致整个区块的 getBlock 失效。
其他 transaction 也不会返回;整个调用都会报错。getTransaction 和 getTransactionsForAddress(与 transactionDetails = full 搭配使用)遇到任何 v1 transaction 时也会以相同方式失败。
修复方法:
- 将 SDK 升级到能够解码 v1 的版本。各客户端正在陆续添加支持。如果你的 SDK 尚未发布 transaction v1 支持,请留意其发行说明中的 transaction v1 或 SIMD-0385。
- 常用 JS 客户端:@solana/kit 8.0+、@solana/web3.js v3
- 常用 Rust crate:solana-rpc-client-api 4.2+、solana-client 4.2+、solana-transaction 4.2+、solana-compute-budget 4.2+、其他 Anza crate。
- 在每次 getBlock、getTransaction 和 getTransactionsForAddress 调用中设置
maxSupportedTransactionVersion: 1(与transactionDetails = full搭配使用) - 在每个 Enhanced WebSockets transactionSubscribe 订阅的 options 对象中设置
maxSupportedTransactionVersion: 1(同时将transactionDetails设为full或accounts)。从文档示例复制的订阅会将其设为0;请升级为1
{
"jsonrpc": "2.0",
"id": "1",
"method": "getTransactionsForAddress",
"params": [
"Vote111111111111111111111111111111111111111",
{
"transactionDetails": "full",
"sortOrder": "desc",
"filters": {
"status": "succeeded"
},
"maxSupportedTransactionVersion": 1
}
]
}
该参数声明你的客户端能够处理的最高版本。现在设置它是安全的,也不会改变 legacy 和 v0 transaction 的返回方式。
Transaction v1 计算预算故障
v1 transaction 将计算上限和优先费存储在 transactionConfig 对象中,而不是 ComputeBudget 指令中。通过匹配这些指令来检测费用的费用仪表板和优先费估算器,会在不报错的情况下将每笔 v1 transaction 都读取为支付零费用。
修复方法:
改为从 transaction 配置中的新 priorityFee 字段读取值。请注意,新的 priorityFee 字段表示以 lamports 计的总费用,而不是每个计算单元的价格。
"message": {
"instructions": ["… no ComputeBudget instruction here …"],
"recentBlockhash": "...",
"transactionConfig": {
"computeUnitLimit": 200000,
"heapSize": null,
"loadedAccountsDataSizeLimit": 200000,
"priorityFee": 50000
}
}
未更改的账户不再发出更新
Agave 4.2 仅在账户实际被写入时发出账户事件。对于 LaserStream gRPC 和 WSS 账户订阅,这意味着事件数量减少约 80%。
修复方法:
- 如果你将 transaction 与账户更新进行匹配,请不要再等待每个可写账户发出更新;应将缺失的更新视为“该账户未发生变化”。只有费用支付方保证会更新,因为它始终需要支付费用。
- 如果你将更新频率用作健康信号,请对经常被锁定但很少发生变化的账户移除这项检查。安静的账户也是健康的;它不再发出事件,只是因为没有发生变化。
新的 rewardType 值
现已完成停用的质押账户,会通过新的 rewardType 值 DeactivatedStake,在 getBlock 和 blockSubscribe 奖励数组中收到最终付款。奖励对象的结构与 Agave 4.1 完全相同,因此只接受已知奖励类型的解析器会跳过 DeactivatedStake 付款,且不会报错。
getInflationReward 不受影响;只有在你自行解析原始奖励数组时才存在此风险。
修复方法:
将 DeactivatedStake 添加到解析器接受的 rewardType 值中,并记录所有无法识别的值,而不是丢弃记录。
{
"pubkey": "...",
"lamports": 10000000,
"postBalance": 50000000000,
"rewardType": "DeactivatedStake",
"commission": null,
"commissionBps": 500
}
Token-2022:移除一个字段,扩展解析范围
在 jsonParsed 响应中,depositConfidentialTransfer 和 withdrawConfidentialTransfer 不再包含 source 和 destination,改为使用单个 account 字段。旧标签并不准确;每条指令只会涉及一个代币账户。
修复方法是更新 Token-2022 解析器,使其支持新字段。
{
"parsed": {
"type": "depositConfidentialTransfer",
"info": {
"account": "6XVfUq9jZQtBfqcm1Rz8fBhViyoyzWiEUAaWnQ9AmXeR",
"mint": "8fJ7bCZo2vZ3vAnyCBQgZuLYuTX1qPnKvsMKotk92B2J",
"amount": 42,
"decimals": 9,
"owner": "F7yLk3s2iZDPBBvSNbXAaKPBnQ9vXqSSSzYUCGeUFhXn"
}
}
}
Permissioned burn、unwrapLamports、confidentialBurn 和批量操作现在会以解析后的 JSON 返回,而不是原始字节。
{
"parsed": {
"type": "unwrapLamports",
"info": {
"source": "9rr9Xh6PXPKcVqbCB1qxGDWRUJJAdguqcUqVuUqEjqQK",
"destination": "BhU2wDgmvvMNC1vTSU4aG7BvW26MoTMSDL63hcMqziGL",
"amount": "1000000",
"authority": "F7yLk3s2iZDPBBvSNbXAaKPBnQ9vXqSSSzYUCGeUFhXn"
}
}
}
此前,包含无法识别扩展的 mint 会返回空的 extensions 数组;在 Agave 4.2 上,该数组会包含数据。
如果你的代码将空数组视为“没有扩展”,请做好该数组开始出现值的准备。
"extensions": [
{
"extension": "transferFeeConfig",
"state": {
"transferFeeConfigAuthority": "...",
"withdrawWithheldAuthority": "...",
"withheldAmount": 0,
"olderTransferFee": {...},
"newerTransferFee": {...}
}
},
{
"extension": "permissionedBurnConfig",
"state": {
"authority": "3nGhQzXCzoDDvW9pkg8fVLGDrJc23uwFz7qzW26MoTMS"
}
}
]
Slot 时间常量已过时
从 epoch 1020 开始,主网以 350ms 的 slot 运行。下一次缩短计划在 epoch 1024 进行,将降至 300ms(预计 8 月 28 日),最终目标为 200ms。在 slot 到时间的计算中硬编码的 400ms 常量目前已有 12.5% 的偏差,并且每次调整后偏差都会进一步增大。
修复方法:
根据区块时间戳推导时间,或将其设为可配置项,并确保区块开始更快到达时,你的索引器仍能跟上。
验证迁移是否完成
要验证迁移是否完成,请运行代理审计并确认你的依赖项。
运行代理审计
将下面的 skill 复制到你的编码代理中。
对于 Claude Code,将其保存为 .claude/skills/agave-42-readiness/SKILL.md。
对于其他代理,将其粘贴为任务提示词。
---
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, plus every Enhanced WebSockets `transactionSubscribe` subscription. Search all languages, raw JSON-RPC request bodies (`"method": "getBlock"`, `"method": "transactionSubscribe"`), 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-checklist展开收起
确认流依赖项
Rust:声明 yellowstone-grpc-client 13.3.0 和 yellowstone-grpc-proto 12.6.0,然后运行 cargo update。
Go:使用最新的 Yellowstone proto 重新生成,并运行 solana-storage-proto。
LaserStream SDK:JS 0.8.4、Rust 0.6.3、Go 0.2.0 或更高版本均包含支持 v1 的 proto。所有路径上的订阅和过滤器都无需更改。
问题
如果你的集成在 4.2 上表现不同,而本文没有解释原因,请通过 Discord 或支持渠道联系我们。要查看功能级别的详细说明,请阅读 4.2 概览。
相关文章
订阅 Helius
及时了解 Solana 开发的最新动态,并在我们发布新内容时收到更新


