JavaScript SDK 的 v2 重写不是通常意义上的升级。★你今天 import 的几乎每一个符号,要么挪了位置、要么变了形状、要么不存在了。★
对交易机器人来说最要紧的部分,是发送路径和数字类型。
形状变了
v1 给你一个带方法的对象。v2 给你一堆可组合的函数。
// ★v1:一个对象干所有事。★
const connection = new Connection(url, "confirmed");
const balance = await connection.getBalance(pubkey);
// ★v2:组合式函数,可 tree-shaking。★
import { createSolanaRpc, address } from "@solana/web3.js";
const rpc = createSolanaRpc(url);
const { value: balance } = await rpc.getBalance(address(addr)).send();
★注意那个 .send()。★ 在 v2 里,一次 RPC 调用先构建出一个请求对象,只有你发送它时才执行。这正是"按调用配置传输层"得以实现的原因,也是迁移期间"我的调用什么都没返回"最常见的来源 —— 一个从没被发送的请求,看起来就像一个返回了 undefined 的调用。
实际收益是包体积。一个只 import 三个函数的前端,打包进去的就是三个函数而不是整个库 —— 这对 web 应用有意义,对后端机器人意义很小。
bigint 取代 number
★这是最可能引入静默 bug 的改动。★
// v1:number —— 超过 2^53 丢精度
const lamports: number = await connection.getBalance(pubkey);
// ★v2:bigint —— 精确★
const { value } = await rpc.getBalance(address(addr)).send();
const lamports: bigint = value;
v2 的选择是对的 —— lamport 金额和 u64 代币余额确实超出 JavaScript number 能精确表示的范围。但两者混用会抛异常,而不是自动转换:
lamports - 5000 // ★TypeError:不能混用 BigInt 和其它类型★
lamports - 5000n // ★正确★
★迁移算术才是真正的工作量,而编译器会抓到其中大部分。★ 它抓不到的,是你为了消掉一个报错而加上的 Number(bigint) 转换 —— 那恰好把 v2 想要消除的精度损失又放了回来。
如果你的 SDK 版本已经定了、交易还是会错过,免费领个 BoltTx key 改一行就能测测提交路径。
发送路径的重写
// ★v1★
const sig = await connection.sendRawTransaction(tx.serialize(), {
skipPreflight: true,
maxRetries: 0,
});
// ★v2★
import {
createSolanaRpc,
getSignatureFromTransaction,
sendAndConfirmTransactionFactory,
} from "@solana/web3.js";
const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });
await sendAndConfirm(signedTx, { commitment: "confirmed", skipPreflight: true });
★工厂模式是结构性的差别。★ v2 希望你把依赖接好、一次性建出一个发送器,而不是每次在一个 connection 上调方法。
采用内置确认之前,有两件事值得知道:
它需要 rpcSubscriptions。 v2 的确认是基于订阅而不是基于轮询的,所以你要在 HTTP 端点之外再配一个 WebSocket 端点。
它替你做了重试决定。 ★对交易机器人来说,重试循环是策略,不是水管★ —— 重发到 blockhash 过期、上链失败不退避、收敛到终态。如果你已经把那套调好了,就留着自己的循环,只用 v2 的发送原语。
发送之前就知道签名
一个真正的改进:
const sig = getSignatureFromTransaction(signedTx); // ★不发网络请求★
签名是由已签名字节确定性推出的,而 v2 把这一点直接暴露了出来。发送后丢失响应不再含糊 —— 你手上已经有可以去查的签名,这消除了通往重复执行最常见的那条路径。
这在 v1 里一直可以做到(把第一个签名做 base58 编码),v2 只是让它变得显而易见。
什么完全没变
★链上的一切都没变。★
- 1232 字节的交易上限
- account meta、
isSigner和isWritable - 大约 150 个区块的 blockhash 窗口
- compute budget 指令,以及手续费的计算方式
- PDA 推导、CPI 深度、租金豁免
★两个 SDK 构建出来的交易,产出的字节和签名完全相同。★ 这正是渐进迁移安全的原因: v1 和 v2 的代码可以对着同一个钱包并行运行,而一个版本签好的交易可以由另一个版本重发。
要不要迁
值得:
- ★包体积是真实约束的前端★
- 新项目用 v2,因为生态最终会走到那边
- 已经在和
number精度问题搏斗的代码库
不急:
- ★一个正常工作的后端机器人★ —— v1 仍然能用,而链没有变
- 任何依赖尚未迁移的库的东西
★生态约束通常是决定性因素。★ Anchor 客户端、钱包适配器、DEX SDK 各按各的节奏迁移,如果某个依赖仍然只认 v1 类型,项目就被钉在原地 —— 写适配层的力气,会超过迁移本身省下的那点。
一条合理的中间路线: 能用的地方留着 v1,新模块按 v2 写,让边界是一笔序列化好的交易 —— 而那是两个版本产出完全一致的东西。
上链应该是什么水平
我们投递节点上的真实交易:中位确认 336 毫秒 —— 不到一个 slot。
★没有任何 SDK 版本能改变那个数字。★ 序列化和签名是微秒级的,而网络时间是按 slot 计的 —— 所以迁移是一个代码质量决策,不是一个性能决策。
BoltTx 在这里的位置
我们接受两个 SDK 产出的字节,因为它们是同样的字节。
提交走我们自建的四区域投递节点,带 SWQoS 路由,不暴露公共 mempool —— 交易在落块之前、传输途中观察不到。★在渐进迁移期间,把 v1 和 v2 的代码指向同一个端点是安全的 —— 相同字节产出相同签名,而一个签名最多被收录一次。★
你在本地签名。我们不托管资金、不代签、不修改交易内容。tip 带在交易里、从你自己的钱包链上支付,交易失败时跟着一起 revert —— 这是 Solana 原子交易的机制决定的。只有到达链上的交易才计费。
免费领 API key,没有月费:
// v1
const connection = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");
// v2
const rpc = createSolanaRpc("https://la.bolttx.io/?api-key=YOUR_KEY");
常见问题
web3.js v1 和 v2 有什么区别?
v1 暴露一个带方法的 Connection 对象;v2 暴露可组合、可 tree-shaking 的函数,没有中心对象。数字也从 number 变成了 bigint。
必须迁到 web3.js v2 吗? 不必须。v1 仍然能用,而链没有变。 为前端包体积或精度正确性而迁,不要因为后端机器人"需要"而迁。
我的 v2 RPC 调用为什么什么都没返回?
多半是忘了 .send()。v2 里一次调用只是构建请求对象、发送时才执行,所以一个没发送的请求看起来像返回了 undefined。
迁移之后为什么报 BigInt 类型错误?
因为 v2 返回 bigint,而 JavaScript 拒绝把它和 number 混用。用 5000n 这样的 bigint 字面量,避免那种会把精度损失放回来的 Number() 转换。
bigint 真的比 number 好吗?
对 lamport 和代币金额是的,它们确实超出 number 能精确表示的范围。它防住的精度 bug 只在大额余额上出现,这正是它能躲过测试的原因。
v2 的发送有什么不同?
你用工厂把依赖接好、建出一个发送器,而不是在 connection 上调方法。内置的确认是基于订阅的,需要一个 rpcSubscriptions 端点。
该用 v2 内置的确认吗? 如果你已经有调好的重试循环就不该。对交易机器人来说重试行为是策略 —— 重发到过期、上链失败不退避 —— 所以留着你的循环,只用 v2 的发送原语。
v2 里能在发送之前拿到签名吗?
能,用 getSignatureFromTransaction,不需要网络调用。这消除了丢失响应带来的含糊,因为你手上已经有可以去查的签名。
v1 和 v2 产出的交易不一样吗? 一样。对同样的指令,两者产出完全相同的字节和签名 —— 这正是并行运行和渐进迁移安全的原因。
能在同一个代码库里同时用 v1 和 v2 吗? 能。让边界是一笔序列化好的交易,因为两个版本产出的它完全一致。这让你能用 v2 写新模块,而不必重写能用的 v1 代码。
v2 会让我的交易更快落块吗? 不会。序列化和签名是微秒级的,而网络时间按 slot 计。 落块速度来自手续费、路由和重试行为,这些 SDK 一个都改变不了。
迁移时最常出问题的是什么?
混用 bigint 和 number 的算术,以及忘记 .send()。 前一类编译器会抓到 —— 这就是这次迁移繁琐但不危险的原因。
Anchor 和 web3.js v2 兼容吗? 取决于你用的版本,而生态库各按各的节奏迁移。一个仍然期待 v1 类型的依赖,通常就是迁移被推迟的原因。
v2 里地址有什么不同?
PublicKey 被一个产出带标记字符串类型的 address() 辅助函数取代。它更轻,也意味着地址处理变成了类型层面的事,而不是一个类实例。
tree-shaking 在这里有什么好处? 一个只 import 三个函数的前端,打包的就是三个函数而不是整个库。对 web 应用是实打实的收益,对后端机器人几乎无关。
新项目该从 v2 起步吗? 一般该,前提是你依赖的库支持它。从那里起步能免掉日后的迁移,但一个仍然要求 v1 的依赖会盖过这个好处。