Skip to main content

Solana 的 JavaScript 客户端

鱼雪

什么是 Solana-Web3.js?

Solana-Web3.js 库旨在提供对 Solana 的全面覆盖。

该库建立在 Solana 的 JSON RPC API 之上

可以在 @solana/web3.js 文档 找到完整文档。

常用术语

  • Program无状态的可执行代码,用于解释指令。程序能够根据提供的指令执行操作
  • Instruction客户端可以包含在交易中的程序的最小单位。 在其处理代码中,一条指令可能包含一个或多个跨程序调用。
  • Transaction:由客户端使用一个或多个密钥对签名的一个或多个指令,以原子方式执行,仅有两种可能结果:成功或失败。

更多术语请参见 Solana 术语表.

入门

安装

使用 yarn:

yarn add @solana/web3.js

使用 npm:

npm install --save @solana/web3.js

浏览器捆绑包:

<script src="https://unpkg.com/@solana/web3.js@latest/lib/index.iife.js"></script>
<script src="https://unpkg.com/@solana/web3.js@latest/lib/index.iife.min.js"></script>

用法

JavaScript:

const solanaWeb3 = require("@solana/web3.js");
console.log(solanaWeb3);

ES6:

import * as solanaWeb3 from "@solana/web3.js";
console.log(solanaWeb3);

浏览器捆绑包:

<script>
// solanaWeb3 是由捆绑包脚本提供的全局命名空间
console.log(solanaWeb3);
</script>

快速开始

连接到钱包

要让用户使用您的 dApp 或 Solana 应用,他们需要获取他们的密钥对

密钥对是一个包含匹配的公钥和私钥的实体,用于签署交易

获取密钥对的两种方式

  1. 生成新的密钥对
  2. 使用密钥(secret key)获得密钥对

生成新的密钥对:

const { Keypair } = require("@solana/web3.js");

let keypair = Keypair.generate();

通过密钥获得密钥对:

const { Keypair } = require("@solana/web3.js");

let secretKey = Uint8Array.from([
202, 171, 192, 129, 150, 189, 204, 241, 142, 71, 205, 2, 81, 97, 2, 176, 48,
81, 45, 1, 96, 138, 220, 132, 231, 131, 120, 77, 66, 40, 97, 172, 91, 245, 84,
221, 157, 190, 9, 145, 176, 130, 25, 43, 72, 107, 190, 229, 75, 88, 191, 136,
7, 167, 109, 91, 170, 164, 186, 15, 142, 36, 12, 23,
]);

let keypair = Keypair.fromSecretKey(secretKey);

目前许多钱包允许用户通过各种扩展或网络钱包带入他们的密钥对。

一般建议使用钱包而不是密钥对来签署交易

钱包在 dApp 和密钥对之间创建了一个分离层,确保 dApp 永远不会访问密钥

可以通过 wallet-adapter 库找到连接外部钱包的方法。

创建和发送交易

要与 Solana 上的程序交互,需要创建、签署并发送交易到网络

交易是带有签名的指令集合

指令在交易中的顺序决定了它们的执行顺序

使用 Transaction 对象创建交易,并添加所需的消息、地址或指令

以下是一个转账交易的示例:

const {
Keypair,
Transaction,
SystemProgram,
LAMPORTS_PER_SOL,
} = require("@solana/web3.js");

let fromKeypair = Keypair.generate();
let toKeypair = Keypair.generate();
let transaction = new Transaction();

transaction.add(
SystemProgram.transfer({
fromPubkey: fromKeypair.publicKey,
toPubkey: toKeypair.publicKey,
lamports: LAMPORTS_PER_SOL,
}),
);

上述代码创建了一个准备签名并广播到网络的交易。

SystemProgram.transfer 指令被添加到交易中, 包含要发送的 lamports 数量,以及 tofrom 的公钥。

签署交易并发送到网络

const {
sendAndConfirmTransaction,
clusterApiUrl,
Connection,
} = require("@solana/web3.js");

let keypair = Keypair.generate();
let connection = new Connection(clusterApiUrl("testnet"));

sendAndConfirmTransaction(connection, transaction, [keypair]);

上述代码使用 SystemProgramTransactionInstruction 创建交易,并将其发送到网络。

使用 Connection 定义连接到的 Solana 网络,主要是 mainnet-betatestnetdevnet

与自定义程序交互

在 Solana 上,所有操作都与不同的程序交互,包括转账交易。

Solana 上的程序目前用 Rust 或 C 编写。

例如,SystemProgramallocate 方法签名如下:

pub fn allocate(
pubkey: &Pubkey,
space: u64
) -> Instruction

要与程序交互,必须首先知道所有将要交互的账户

必须提供程序将交互的每个账户

并指定账户是否是签名者(`isSigner`)或可写(`isWritable`)

在上面的allocate方法中,需要一个单独的帐户公钥,以及要分配的空间量

我们知道allocate方法通过在帐户内分配空间来写入,因此需要isWritable

当您指定运行指令的帐户时,需要isSigner

在这种情况下,签署者是调用在其内部分配空间的帐户

让我们看看如何使用solana-web3.js调用此指令:

调用 allocate 方法示例:

let keypair = web3.Keypair.generate();
let payer = web3.Keypair.generate();
let connection = new web3.Connection(web3.clusterApiUrl("testnet"));

let airdropSignature = await connection.requestAirdrop(
payer.publicKey,
web3.LAMPORTS_PER_SOL,
);

await connection.confirmTransaction({ signature: airdropSignature });

首先,我们设置账户 Keypair 和连接,以便在测试网上进行分配。

我们还创建一个支付方 Keypair 并进行空投一些 Sol,以便支付分配交易。

let allocateTransaction = new web3.Transaction({
feePayer: payer.publicKey,
});
let keys = [{ pubkey: keypair.publicKey, isSigner: true, isWritable: true }];
let params = { space: 100 };

我们创建了交易allocateTransactionkeysparams 对象。

在创建交易时,feePayer 是一个可选字段,用于指定谁支付交易费,默认为交易中第一个签名者的公钥。

keys 代表程序的allocate函数将与之交互的所有账户。

由于allocate函数还需要空间,我们创建了params,在稍后调用allocate函数时使用。

let allocateStruct = {
index: 8,
layout: struct([u32("instruction"), ns64("space")]),
};

上述内容是使用 @solana/buffer-layoutu32ns64 创建的,以便促进有效载荷的创建。

分配函数接受参数空间。为了与函数交互,我们必须将数据提供为缓冲区格式。

buffer-layout 库有助于为 Solana 上的 Rust 程序正确分配缓冲区并对其进行编码解释。

让我们来分解这个结构。

{
index: 8, /* <-- */
layout: struct([
u32('instruction'),
ns64('space'),
])
}

index设置为 8,因为分配函数位于 SystemProgram 的指令枚举中的第 8 个位置。

/* https://github.com/solana-labs/solana/blob/21bc43ed58c63c827ba4db30426965ef3e807180/sdk/program/src/system_instruction.rs#L142-L305 */
pub enum SystemInstruction {
/** 0 **/CreateAccount {/**/},
/** 1 **/Assign {/**/},
/** 2 **/Transfer {/**/},
/** 3 **/CreateAccountWithSeed {/**/},
/** 4 **/AdvanceNonceAccount,
/** 5 **/WithdrawNonceAccount(u64),
/** 6 **/InitializeNonceAccount(Pubkey),
/** 7 **/AuthorizeNonceAccount(Pubkey),
/** 8 **/Allocate {/**/},
/** 9 **/AllocateWithSeed {/**/},
/** 10 **/AssignWithSeed {/**/},
/** 11 **/TransferWithSeed {/**/},
/** 12 **/UpgradeNonceAccount,
}

接下来是u32('instruction')

{
index: 8,
layout: struct([
u32('instruction'), /* <-- */
ns64('space'),
])
}

在使用它调用指令时,分配结构中的布局必须始终首先具有 u32('instruction')

{
index: 8,
layout: struct([
u32('instruction'),
ns64('space'), /* <-- */
])
}

ns64('space')allocate 函数的参数。 您可以在 Rust 中的原始 allocate 函数中看到,space 的类型是 u64u64 是无符号 64 位整数。

JavaScript 默认只提供最多 53 位整数。

ns64 来自 @solana/buffer-layout,用于帮助在 Rust 和 JavaScript 之间进行类型转换。

您可以在 solana-labs/buffer-layout 中找到更多关于 Rust 和 JavaScript 之间的类型转换。

let data = Buffer.alloc(allocateStruct.layout.span);
let layoutFields = Object.assign({ instruction: allocateStruct.index }, params);
allocateStruct.layout.encode(layoutFields, data);

使用先前创建的缓冲布局,我们可以分配数据缓冲区

然后,我们分配我们的参数 { space: 100 },以便它正确映射到布局,并将其编码到数据缓冲区中。

现在数据已准备好发送到程序。

allocateTransaction.add(
new web3.TransactionInstruction({
keys,
programId: web3.SystemProgram.programId,
data,
}),
);

await web3.sendAndConfirmTransaction(connection, allocateTransaction, [
payer,
keypair,
]);

最后,我们将包含所有账户密钥、付款人、数据和程序ID的交易指令添加到交易中,并将交易广播到网络。

完整代码:

const { struct, u32, ns64 } = require("@solana/buffer-layout");
const { Buffer } = require("buffer");
const web3 = require("@solana/web3.js");

let keypair = web3.Keypair.generate();
let payer = web3.Keypair.generate();

let connection = new web3.Connection(web3.clusterApiUrl("testnet"));

let airdropSignature = await connection.requestAirdrop(
payer.publicKey,
web3.LAMPORTS_PER_SOL,
);

await connection.confirmTransaction({ signature: airdropSignature });

let allocateTransaction = new web3.Transaction({
feePayer: payer.publicKey,
});
let keys = [{ pubkey: keypair.publicKey, isSigner: true, isWritable: true }];
let params = { space: 100 };

let allocateStruct = {
index: 8,
layout: struct([u32("instruction"), ns64("space")]),
};

let data = Buffer.alloc(allocateStruct.layout.span);
let layoutFields = Object.assign({ instruction: allocateStruct.index }, params);
allocateStruct.layout.encode(layoutFields, data);

allocateTransaction.add(
new web3.TransactionInstruction({
keys,
programId: web3.SystemProgram.programId,
data,
}),
);

await web3.sendAndConfirmTransaction(connection, allocateTransaction, [
payer,
keypair,
]);

总结