Solana JavaScript SDK 2.0 版,是在使用 web3.js 开发 Solana 应用程序时,对许多痛点进行了优化。
web3.js(1.x)API 的面向对象设计使优化编译器无法从生产构建中,“tree-shake”未使用的代码。无论您在应用程序中使用了多少 web3.js API,到目前为止,都必须将其全部打包。
在这里了解有关 tree-shaking 的更多信息:
一个 API 不能是树 shaken 的例子是 Connection 类。它有几十个方法,但因为它是一个类,你别无选择,只能将每个方法都包含在应用程序的最终包中,无论你实际使用了多少方法。
微小的 JavaScript 捆绑包可能会导致部署到 Cloudflare 或 AWS Lambda 等云计算提供商时出现问题。由于下载和 JavaScript 解析时间较长,它们还会影响 Web 应用程序的启动性能。
2.0 版本是完全树抖动的,并将通过构建时检查强制执行。优化编译器现在可以消除应用程序不使用的库部分。
新库本身由@solana组织下的几个较小的模块化包组成,包括:
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/accounts</font>
:用于获取和解码账户
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/codecs</font>
:用于从一组原语中编写数据(反)序列化器或构建自定义数据(反)序列化器
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/errors</font>
:用于识别和细化<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana</font>
命名空间中抛出的编码错误
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc</font>
:用于发送 RPC 请求
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc-subscriptions</font>
:用于订阅 RPC 通知
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/signers</font>
:用于构建消息和/或交易签名者对象
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/sysvars</font>
:用于获取和解码 sysvar 帐户
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/transaction-messages</font>
:用于构建和转换 Solana 交易消息对象
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/transactions</font>
:用于编译和签署交易以提交给网络
其中一些包本身由较小的包组成。例如,由(针对核心 JSON RPC 规范类型)、(针对 Solana 特定的 RPC 方法)、(针对默认 HTTP 传输)等<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc</font>
组成。<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc-spec</font>
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc-api</font>
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc-transport-http</font>
开发人员可以使用主库()中的默认配置<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/web3.js@next</font>
,或者导入任何需要通过组合进行定制的子包。
根据您的用例和对某些应用程序行为的容忍度,您可能希望将您的应用程序配置为与其他开发人员进行不同的权衡。web3.js(1.x)API 对所有开发人员强加了一套严格的通用默认设置,其中一些是不可能改变的。
到目前为止,无法自定义 web3.js 一直是令人十分伤心的原因:
Mango 团队希望定制交易确认策略,但所有这些功能都隐藏在 confirmTransaction 后面——一种静态的 Connection 方法。这是 GitHub 上 confirmTransaction 的代码。
Solana 开发人员'mPaella'希望我们在 RPC 中添加一个功能,在主 URL 失败的情况下,该功能将故障转移到一组备份 URL。
Solana 开发人员“epicface”希望在 RPC 传输中为自动时间窗口批处理提供一流的支持。这是他们的拉取请求。
许多人表示需要为失败的请求或事务定制重试逻辑。这是来自“dafyddd”的 pull 请求和来自“abrkn”的另一个 pull 请求,它们试图修改重试逻辑以适应各自的用例。
2.0 版本暴露了更多的内部内容,特别是在与 RPC 通信的情况下,并允许有意愿的开发人员从默认实现中组合新的实现,这些实现体现了几乎无限的自定义数组。
组成 web3.js 的各个模块以默认配置组装,让人联想到作为 npm 包@solana一部分的遗留库/web3.js@next,但那些希望以不同配置组装它们的人可以这样做。
通用类型在许多地方提供,允许您指定新的功能,通过组合和超类型对每个 API 进行扩展,并鼓励您创建自己的更高层次的固执己见的抽象。
事实上,我们希望你这样做,并将其中一些开源,供其他有类似需求的人使用。
现代 JavaScript 功能的进步为加密应用程序的开发人员提供了机会,例如使用本机 Ed25519 密钥并将大值表示为本机 bigint 的能力。
Web 孵化器社区小组主张在 Web Crypto API 中添加 Ed25519 支持,并且大多数现代 JavaScript 运行时都已获得支持。
引擎对 bigint 值的支持也变得司空见惯。JavaScript 中较旧的数字基元的最大值为 2^53-1,而 Rust 的 u64 可以表示高达 2^64 的值。
2.0 版本消除了 Ed25519 密码学、大量 polyfills 等的用户空间实现,转而支持自定义实现或使用原生 JavaScript 功能,从而减小了库的大小。它不依赖于第三方。
web3.js(1.x)的面向对象、基于类的架构导致了不必要的包膨胀。你的应用程序别无选择,只能捆绑一个类的所有功能和依赖关系,无论你在运行时实际使用了多少方法。
基于类的架构也给触发双包危险的开发人员带来了独特的风险。这描述了如果你同时为 CommonJS 和 ES 模块构建,你可能会遇到的情况。当依赖树中存在同一类的两个副本时,就会出现这种情况,导致 instanceof 等检查失败。这引入了加重和难以调试的问题。
阅读更多关于双包装危险的信息:
NodeJS:双包危险
2.0 版本没有实现任何类(SolanaError 类除外),并在函数边界实现了尽可能薄的接口。
2.0 版和旧版 1.x 之间的统计对比。
1.x (Legacy) | 2.0 | +/- % | |
---|---|---|---|
Total minified size of library | 81 KB | 57.5 KB | -29% |
Total minified size of library (when runtime supports Ed25519) | 81 KB | 53 KB | -33% |
Bundled size of a web application that executes a transfer of lamports | 111 KB | 23.9 KB | -78% |
Bundled size of a web application that executes a transfer of lamports (when runtime supports Ed25519) | 111 KB | 18.2 KB | -83% |
Performance of key generation, signing, and verifying signatures (Brave with Experimental API flag) | 700 ops/s | 7000 ops/s | +900% |
First-load size for Solana Explorer | 311 KB | 228 KB | -26% |
重新设计的库在很大程度上通过使用现代 JavaScript API 实现了这些加速和捆绑包大小的减少。
为了验证我们的工作,我们在 Solana Explorer 的主页上用新的 2.0 库替换了旧的 1.x 库。在不删除任何功能的情况下,首次加载包的总大小下降了 26%。如果你想深入了解,这里有一个 Callum McIntyre 的 X 线程。
以下是如何使用新库与 RPC 交互、配置网络传输、使用 Ed25519 密钥以及序列化数据的概述。
2.0 版附带了 JSON RPC 规范的实现和Solana JSON RPC的类型规范。
负责管理与 RPC 通信的主要包是<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc</font>
。但是,此包利用更细粒度的包将 RPC 逻辑分解为更小的部分。即,这些包是:
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc</font>
:包含与发送 Solana RPC 调用相关的所有逻辑。
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc-api</font>
:使用类型描述所有 Solana RPC 方法。
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc-transport-http</font>
:提供使用 HTTP 请求的 RPC 传输的具体实现。
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc-spec</font>
:定义用于发送 RPC 请求的 JSON RPC 规范。
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc-spec-types</font>
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc</font>
:和两者使用的共享 JSON RPC 规范类型和帮助程序<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc-subscriptions</font>
(在下一节中描述)。
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc-types</font>
<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc</font>
:和使用的共享 Solana RPC 类型和帮助程序<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc-subscriptions</font>
。
主<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/web3.js</font>
包重新导出<font style="color:rgb(34, 34, 34);background-color:rgb(242, 244, 248);">@solana/rpc</font>
包,因此,接下来我们将直接从库中导入 RPC 类型和函数。
自己先创建一个前端项目,然后引入相关的依赖
npm install --save @solana/web3.js@next
所有的代码示例托管到了,github
createSolanaRpc
函数使用应满足大多数用例的默认 HTTP 传输与 RPC 服务器通信。您可以提供自己的传输或包装现有的传输,以您认为合适的任何方式与 RPC 服务器通信。在下面的示例中,我们显式创建了一个传输,并使用它通过 createSolanaRpcFromTransport 函数创建一个新的 RPC 客户端。
import { createSolanaRpcFromTransport, createDefaultRpcTransport } from '@solana/web3.js';
// Create an HTTP transport or any custom transport of your choice.
const transport = createDefaultRpcTransport({ url: 'https://api.devnet.solana.com' });
// Create an RPC client using that transport.
const rpc = createSolanaRpcFromTransport(transport);
// ^? Rpc<SolanaRpcApi>
// Send a request.
const slot = await rpc.getSlot().send();
点击按钮弹出如下信息
自定义传输可以实现专门的功能,如协调多个传输、实现重试等。让我们来看一些具体的例子。
“轮询”传输是一种按顺序将请求分发到端点列表的传输。
官方给出的 demo
import { createDefaultRpcTransport, createSolanaRpcFromTransport, type RpcTransport } from '@solana/web3.js';
// Create an HTTP transport for each RPC server.
const transports = [
createDefaultRpcTransport({ url: 'https://mainnet-beta.my-server-1.com' }),
createDefaultRpcTransport({ url: 'https://mainnet-beta.my-server-2.com' }),
createDefaultRpcTransport({ url: 'https://mainnet-beta.my-server-3.com' }),
];
// Set up the round-robin transport.
let nextTransport = 0;
async function roundRobinTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<TResponse> {
const transport = transports[nextTransport];
nextTransport = (nextTransport + 1) % transports.length;
return await transport(...args);
}
// Create an RPC client using the round-robin transport.
const rpc = createSolanaRpcFromTransport(roundRobinTransport);
我们改造一下
// 创建多个RPC客户端用于轮询(这里简化为创建两个示例)
async createRpcClients() {
const rpc1 = createSolanaRpc('http://127.0.0.1:8899');
const rpc2 = createSolanaRpc('http://127.0.0.1:8898');
this.rpcClients.push(rpc1, rpc2);
},
async roundRobinSlotRequest() {
if (this.rpcClients.length === 0) {
await this.createRpcClients();
}
try {
const currentRpc = this.rpcClients[this.currentRpcIndex];
this.slot = await currentRpc.getSlot().send();
alert(`通过轮询获取到的slot信息为: ${this.slot}`);
// 更新当前使用的RPC客户端索引,实现轮询效果
this.currentRpcIndex = (this.currentRpcIndex + 1) % this.rpcClients.length;
} catch (error) {
alert(`轮询获取slot信息时出错: ${error}`);
}
}
},
通过多次点击按钮弹,会分别弹出如下信息
分片传输是一种分布式传输,它根据请求本身的某些信息向特定服务器发送请求。下面是一个根据方法名称向不同服务器发送请求的示例:
官方的 demo
import { createDefaultRpcTransport, createSolanaRpcFromTransport, type RpcTransport } from '@solana/web3.js';
// Create multiple transports.
const transportA = createDefaultRpcTransport({ url: 'https://mainnet-beta.my-server-1.com' });
const transportB = createDefaultRpcTransport({ url: 'https://mainnet-beta.my-server-2.com' });
const transportC = createDefaultRpcTransport({ url: 'https://mainnet-beta.my-server-3.com' });
const transportD = createDefaultRpcTransport({ url: 'https://mainnet-beta.my-server-4.com' });
// Function to determine which shard to use based on the request method.
function selectShard(method: string): RpcTransport {
switch (method) {
case 'getAccountInfo':
case 'getBalance':
return transportA;
case 'getLatestBlockhash':
case 'getTransaction':
return transportB;
case 'sendTransaction':
return transportC;
default:
return transportD;
}
}
// Create a transport that selects the correct transport given the request method name.
async function shardingTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<TResponse> {
const payload = args[0].payload as { method: string };
const selectedTransport = selectShard(payload.method);
return (await selectedTransport(...args)) as TResponse;
}
// Create an RPC client using the sharding transport.
const rpc = createSolanaRpcFromTransport(shardingTransport);
改造后
// 创建多个传输对象(用于sharding)
createTransports() {
this.transportA = createDefaultRpcTransport({url: 'https://mainnet-beta.my-server-1.com'});
this.transportB = createDefaultRpcTransport({url: 'https://mainnet-beta.my-server-2.com'});
this.transportC = createDefaultRpcTransport({url: 'https://mainnet-beta.my-server-3.com'});
this.transportD = createDefaultRpcTransport({url: 'https://mainnet-beta.my-server-4.com'});
},
// 根据请求方法选择分片(用于sharding)
selectShard(method) {
switch (method) {
case 'getAccountInfo':
case 'getBalance':
return this.transportA;
case 'getLatestBlockhash':
case 'getTransaction':
return this.transportB;
case 'sendTransaction':
return this.transportC;
default:
return this.transportD;
}
},
// 实现分片传输逻辑(用于sharding)
async shardingTransport(args) {
const payload = args[0].payload;
const method = payload.method;
const selectedTransport = this.selectShard(method);
return await selectedTransport.apply(null, args);
},
// 创建RPC客户端并执行sharding按钮点击操作
async shardingButtonClick() {
if (!this.transportA || !this.transportB || !this.transportC || !this.transportD) {
this.createTransports();
}
// 创建RPC客户端
this.rpc = createSolanaRpcFromTransport(this.shardingTransport);
try {
// 这里可以添加具体要执行的请求操作示例,比如获取账户信息
const accountInfo = await this.rpc.getAccountInfo('your_account_address').send();
alert(`获取到的账户信息为: ${accountInfo}`);
} catch (error) {
alert(`执行sharding操作时出错: ${error}`);
}
},
},
这就是 solana web3.js sdk 2.0 其中一部分新特性的演示。
详情请参考
https://solana-labs.github.io/solana-web3.js/#a-tour-of-the-version-20-api
文中代码托管到了 github