Skip to content

Solana的JavaScript客户端库

Solana-Web3.js是什么?

Solana-Web3.js 库旨在提供对 Solana 的全面支持。该库是基于 Solana JSON RPC API构建的。

你可以在这里找到 @solana/web3.js 库的完整文档。

常见术语

术语定义
程序用于解释指令的无状态可执行代码。程序能够根据提供的指令执行操作。
指令客户端可以包含在交易中的最小程序单元。在处理代码中,一条指令可以包含一个或多个跨程序调用。
交易一个或多个由客户端使用一个或多个密钥对签署并原子执行的指令,只有两种可能的结果:成功或失败。

完整的术语列表,请参阅 Solana 术语

入门指南

安装

yarn

shell
yarn add @solana/web3.js

npm

shell
npm install --save @solana/web3.js

Bundle

html
<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

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

ES6

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

Browser Bundle

js
// solanaWeb3 通过 bundle 脚本提供在全局命名空间中
console.log(solanaWeb3);

快速入门

连接到钱包

为了允许用户在 Solana 上使用你的 dApp 或应用,他们需要访问他们的密钥对。密钥对是一个具有匹配公钥的私钥,用于签署交易。

有两种方法可以获得密钥对:

  1. 生成一个新的密钥对
  2. 使用私钥获取密钥对

你可以通过以下方式获得一个新的密钥对:

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

这将为用户生成一个全新的密钥对,以便在你的应用中进行存储和使用。

你可以允许用户通过文本框输入私钥,并使用 Keypair.fromSecretKey(secretKey) 获取密钥对。

js
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 上的程序交互,你需要创建、签署并将交易发送到网络。交易是带有签名的指令集合。指令在交易中的顺序决定了它们的执行顺序。

在 Solana-Web3.js 中,交易使用 Transaction 对象创建,并添加所需的消息、地址或指令。

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

js
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 数量以及接收发送公钥。

剩下的就是使用密钥对签署交易并将其发送到网络。你可以使用 sendAndConfirmTransaction 来发送交易,如果你希望在交易完成后通知用户或执行某些操作;如果你不需要等待交易确认,可以使用 sendTransaction

js
const {
  sendAndConfirmTransaction,
  clusterApiUrl,
  Connection,
} = require("@solana/web3.js");
 
let keypair = Keypair.generate();
let connection = new Connection(clusterApiUrl("testnet"));
 
sendAndConfirmTransaction(connection, transaction, [keypair]);

上述代码使用 SystemProgram 接受一个 TransactionInstruction,创建一个交易并将其发送到网络。你需要使用 Connection 定义你连接的 Solana 网络,即主网-beta版测试网开发网

与自定义程序交互

上一节介绍了发送基本交易。在 Solana 上,你所做的一切都是与不同的程序交互,包括上一节的转账交易。在现在,Solana 上的程序要么是用 Rust 要么是用 C 编写的。

让我们来看一下 SystemProgram。在 Solana 中分配空间的方法签名在 Rust 中看起来像这样:

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

在 Solana 中,当你想与程序交互时,你必须首先知道你将与哪些账户进行交互。

你必须始终提供每个程序将在指令中交互的账户。不仅如此,你还必须提供账户是否为 isSignerisWritable

在上述 allocate 方法中,需要一个单一的账户 pubkey 以及分配的空间数量。我们知道 allocate 方法通过在账户内分配空间来写入该账户,因此 pubkey 需要是 isWritable。当你指定运行指令的账户时,需要 isSigner。在这种情况下,签名者是调用 allocate 在自己内部分配空间的账户。

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

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 });

首先,我们设置账户密钥对和连接,以便我们有一个账户可以在 测试网 上进行分配。我们还创建了一个付款密钥对,并请求了一些 sol 以便我们可以支付分配交易。

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

我们创建了 allocateTransactionkeysparams 对象。feePayer 是创建交易时的可选字段,用于指定谁支付交易费用,默认为交易中第一个签名者的 pubkeykeys 表示程序的 allocate 函数将与之交互的所有账户。由于 allocate 函数还需要空间,我们创建了 params 以便稍后调用 allocate 函数时使用。

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

上述内容使用 @solana/buffer-layout 中的 u32ns64 创建,以便于创建负载。allocate 函数接受参数 space。为了与该函数交互,我们必须将数据提供为 Buffer 格式。buffer-layout 库有助于分配缓冲区并正确编码,以便 Solana 上的 Rust 程序解析。

让我们分解这个结构。

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

index 被设置为8,因为allocate函数在SystemProgram的指令枚举中位于第8位。

rust
/* 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')

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

在调用指令时,allocate 结构中的layout必须始终首先包含 u32('instruction')

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

ns64('space')allocate 函数的参数。你可以在原始的 Rust allocate 函数中看到,spaceu64 类型。u64 是一个无符号的64位整数。JavaScript 默认仅提供最高53位整数。ns64 来自 @solana/buffer-layout,有助于在 Rust 和 JavaScript 之间进行类型转换。你可以在 solana-labs/buffer-layout.中找到更多 Rust 和 JavaScript 之间的类型转换。

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

使用先前创建的 bufferLayout,我们可以分配一个数据缓冲区。然后我们分配参数 { space: 100 },以便它正确映射到布局,并将其编码到数据缓冲区。现在数据已准备好发送到程序。

js
allocateTransaction.add(
  new web3.TransactionInstruction({
    keys,
    programId: web3.SystemProgram.programId,
    data,
  }),
);
 
await web3.sendAndConfirmTransaction(connection, allocateTransaction, [
  payer,
  keypair,
]);

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

完整代码如下所示:

js
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,
]);