gill:与 Solana 区块链交互的 JavaScript/TypeScript 客户端库

什么是 gill?

gill 是一个用于与 Solana 区块链交互的 JavaScript/TypeScript 客户端库。无论你是在 Node.js、Web 端、React Native 还是其他 JavaScript 环境中构建应用,gill 都能帮助你轻松接入 Solana 网络。

gill 基于 Anza 开发的现代 JavaScript 库 👉@solana/kit(之前称为 “web3.js v2”)构建,使用了相同的类型和函数,因此与 kit 完全兼容。

为什么选择 gill?

与直接使用 @solana/kit 相比,gill 提供了更简洁的 API 和更多实用功能,比如内置的事务构建器、调试模式支持,以及对常见操作(如创建代币、转账等)的高级抽象。这些功能可以显著减少样板代码,提升开发效率。

如何安装 gill?

你可以使用常用的包管理器来安装 gill:

npm install gill
pnpm add gill
yarn add gill

如果你之前使用的是 @solana/kit,只需将导入路径替换为 gill 即可,所有功能保持不变,同时还能享受 gill 的额外功能。

快速开始指南

创建 Solana RPC 连接

首先,你需要建立一个与 Solana 网络的连接。gill 提供了 createSolanaClient 函数来创建 RPC 客户端:

import { createSolanaClient } from "gill";

const { rpc, rpcSubscriptions, sendAndConfirmTransaction } = createSolanaClient({
  urlOrMoniker: "mainnet", // 可以是 "devnet", "localnet" 或自定义 RPC URL
});

使用网络简称(如 “mainnet”)会连接到公共 RPC 端点,但这些端点有速率限制,不建议在生产环境中使用。生产应用应该使用自己的 RPC 提供商提供的 URL。

进行 Solana RPC 调用

建立连接后,你可以使用 rpc 对象调用所有 👉JSON RPC 方法

// 获取当前 slot
const slot = await rpc.getSlot().send();

// 获取最新区块哈希
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

RPC 调用需要调用 .send() 方法才会实际发送请求到 RPC 提供商并获取响应。

你还可以在调用中包含自定义配置,比如使用 JavaScript 的 👉AbortController

const abortController = new AbortController();

// 当用户离开当前页面时中止请求
function onUserNavigateAway() {
  abortController.abort();
}

const slot = await rpc.getSlot().send({ abortSignal: abortController.signal });

生成密钥对和签名器

大多数”签名”操作都需要一个 KeyPairSigner 实例,用于签署交易和消息。

生成一个随机的 KeyPairSigner

import { generateKeyPairSigner } from "gill";

const signer = await generateKeyPairSigner();

这些签名器是不可提取的,意味着无法从实例中获取私钥材料。这是更安全的做法,除非你确实需要保存密钥对,否则强烈推荐使用这种方式。

如果你确实需要可提取的密钥对,gill 也提供了相应功能:

import { generateExtractableKeyPairSigner } from "gill";

const signer = await generateExtractableKeyPairSigner();

警告:使用可提取密钥对本质上不太安全,因为它们允许提取私钥材料。因此,只有在确实需要提取密钥材料(比如要将密钥保存到文件)时才应该使用它们。

创建交易

使用 gill 可以快速创建 Solana 交易:

import { createTransaction } from "gill";

const transaction = createTransaction({
  version,
  feePayer, // 可以是 Address 或 TransactionSigner
  instructions,
  // 强烈建议设置计算预算值以提高交易成功率
  // computeUnitLimit: number,
  // computeUnitPrice: number,
});

创建交易时设置最新区块哈希:

import { createTransaction } from "gill";

const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

const transaction = createTransaction({
  version,
  feePayer,
  instructions,
  latestBlockhash,
});

签名交易

如果你的交易已经通过 createTransaction 设置了最新区块哈希生命周期:

import { createTransaction, signTransactionMessageWithSigners } from "gill";

const transaction = createTransaction(...);
const signedTransaction = await signTransactionMessageWithSigners(transaction);

如果交易没有设置最新区块哈希生命周期,你必须在签名操作之前或期间设置:

import {
  createTransaction,
  createSolanaClient,
  signTransactionMessageWithSigners,
  setTransactionMessageLifetimeUsingBlockhash,
} from "gill";

const { rpc } = createSolanaClient(...);
const transaction = createTransaction(...);

const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

const signedTransaction = await signTransactionMessageWithSigners(
  setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, transaction),
);

模拟交易

在实际发送交易前,你可以先模拟交易以检查可能的问题:

import { createSolanaClient, createTransaction } from "gill";

const { simulateTransaction } = createSolanaClient({
  urlOrMoniker: "mainnet",
});

const transaction = createTransaction(...);
const simulation = await simulateTransaction(transaction);

提供给 simulateTransaction() 的交易可以是已签名或未签名的。

发送和确认交易

使用 sendAndConfirmTransaction 函数发送并确认交易:

import { createSolanaClient, createTransaction, signTransactionMessageWithSigners } from "gill";

const { sendAndConfirmTransaction } = createSolanaClient({
  urlOrMoniker: "mainnet",
});

const transaction = createTransaction(...);
const signedTransaction = await signTransactionMessageWithSigners(transaction);

// 默认确认级别为 'confirmed'
await sendAndConfirmTransaction(signedTransaction);

如果你需要更精细地控制发送和确认过程的配置:

await sendAndConfirmTransaction(signedTransaction, {
  commitment: "confirmed",
  skipPreflight: true,
  maxRetries: 10n,
  // ... 其他配置
});

从已签名交易获取签名

交易被 feePayer 签名后(无论是部分签名还是完全签名),你可以获取交易签名:

import { getSignatureFromTransaction } from "gill";

const signature = getSignatureFromTransaction(signedTransaction);
console.log(signature);
// 示例输出: 4nzNU7YxPtPsVzeg16oaZvLz4jMPtbAzavDfEFmemHNv93iYXKKYAaqBJzFCwEVxiULqTYYrbjPwQnA1d9ZCTELg

由于 Solana 交易 ID 是交易签名数组中的第一项,客户端应用甚至可以在交易发送到网络确认之前就知道签名。

获取 Solana Explorer 链接

gill 提供了便捷的函数来生成 Solana Explorer 链接,用于查看交易、账户或区块信息。

获取交易链接

import { getExplorerLink } from "gill";

const link = getExplorerLink({
  transaction: "4nzNU7YxPtPsVzeg16oaZvLz4jMPtbAzavDfEFmemHNv93iYXKKYAaqBJzFCwEVxiULqTYYrbjPwQnA1d9ZCTELg",
});

如果你有部分或完全签名的交易,甚至可以在发送交易之前获取 Explorer 链接:

import {
  getExplorerLink,
  getSignatureFromTransaction,
  signTransactionMessageWithSigners,
} from "gill";

const signedTransaction = await signTransactionMessageWithSigners(...);
const link = getExplorerLink({
  transaction: getSignatureFromTransaction(signedTransaction),
});

获取账户链接

import { getExplorerLink } from "gill";

const link = getExplorerLink({
  cluster: "devnet",
  account: "nick6zJc6HpW3kfBm4xS2dmbuVRyb5F3AnUvj5ymzR5",
});

获取区块链接

import { getExplorerLink } from "gill";

const link = getExplorerLink({
  cluster: "mainnet",
  block: "242233124",
});

当没有指定 cluster 时,getExplorerLink 函数默认使用 mainnet

计算账户最低租金

计算账户的最低租金余额(数据存储存款费用):

import { getMinimumBalanceForRentExemption } from "gill";

// 不提供 `space` 参数时默认为 0
const rent = getMinimumBalanceForRentExemption();
// 期望值: 890_880n

// 等同于
// getMinimumBalanceForRentExemption(0);

// 也等同于以下网络调用,但这是本地计算不需要网络请求
// const rent = await rpc.getMinimumBalanceForRentExemption(0n).send();

计算特定大小账户的租金:

import { getMinimumBalanceForRentExemption } from "gill";

const rent = getMinimumBalanceForRentExemption(50); // 50 字节
// 期望值: 1_238_880n

目前,账户的最低租金金额是基于 Solana 运行时的静态值计算的。虽然你可以使用 RPC 调用 getMinimumBalanceForRentExemption 来获取这个值,但这会导致网络调用并受到延迟影响。

Node.js 特定功能

gill 包提供了专门用于 Node.js 服务器后端和/或无服务器环境的导入,这些环境可以访问 Node 特定 API(如通过 node:fs 访问文件系统)。

import { ... } from "gill/node"

从文件加载密钥对

import { loadKeypairSignerFromFile } from "gill/node";

// 默认文件路径: ~/.config/solana/id.json
const signer = await loadKeypairSignerFromFile();
console.log("address:", signer.address);

从文件系统钱包 JSON 文件加载 KeyPairSigner,如 👉Solana CLI 输出的文件(即数字 JSON 数组)。

默认加载的是 Solana CLI 的默认密钥对:~/.config/solana/id.json

从特定文件路径加载签名器:

import { loadKeypairSignerFromFile } from "gill/node";

const signer = await loadKeypairSignerFromFile("/path/to/your/keypair.json");
console.log("address:", signer.address);

保存密钥对到文件

将可提取的 KeyPairSigner 保存到本地 JSON 文件(例如 keypair.json)。

import { generateExtractableKeyPairSigner } from "gill";
import { saveKeypairSignerToFile } from "gill/node";

const extractableSigner = await generateExtractableKeyPairSigner();
await saveKeypairSignerToFile(extractableSigner, "/path/to/keypair.json");

从环境变量加载密钥对

从环境进程中的字节加载 KeyPairSigner(例如 process.env[variableName]

import { loadKeypairSignerFromEnvironment } from "gill/node";

// 从 `process.env[variableName]` 存储的字节加载签名器
const signer = await loadKeypairSignerFromEnvironment("MY_KEYPAIR");
console.log("address:", signer.address);

保存密钥对到环境变量文件

将可提取的 KeyPairSigner 保存到本地环境变量文件(例如 .env)。

import { generateExtractableKeyPairSigner } from "gill";
import { saveKeypairSignerToEnvFile } from "gill/node";

const extractableSigner = await generateExtractableKeyPairSigner();
// 默认: envPath = `.env`(在当前工作目录中)
await saveKeypairSignerToEnvFile(extractableSigner, "MY_KEYPAIR");

从环境变量加载 base58 编码的密钥对

从环境进程中存储的 base58 编码密钥对加载 KeyPairSigner

import { loadKeypairSignerFromEnvironmentBase58 } from "gill/node";

// 从 `process.env[variableName]` 存储的 base58 密钥对加载签名器
const signer = await loadKeypairSignerFromEnvironmentBase58("MY_KEYPAIR_BASE58");
console.log("address:", signer.address);

事务构建器

为了简化常见事务的创建,gill 包含了各种”事务构建器”,帮助轻松组装即签即用的事务,这些事务通常需要同时与多个程序交互。

由于每个事务构建器都专注于单个任务,它们可以轻松抽象出各种样板代码,同时帮助创建优化的事务,包括:

  • 设置/推荐默认计算单元限制(当然可以轻松覆盖)以优化事务并提高成功率
  • 在需要时自动派生所需地址
  • 通常推荐安全的默认值和回退设置

所有自动填充的信息也可以手动覆盖,确保你始终有逃生舱口来实现所需的功能。

创建带有元数据的代币

构建一个可以创建带有元数据的代币的事务,可以使用👉原始代币👉代币扩展(token22)程序。

  • 使用原始代币程序(TOKEN_PROGRAM_ADDRESS,默认)创建的代币将使用 Metaplex 的代币元数据程序来处理链上元数据
  • 使用代币扩展程序(TOKEN_2022_PROGRAM_ADDRESS)创建的代币将使用元数据指针扩展

相关指令构建器:getCreateTokenInstructions

import { buildCreateTokenTransaction } from "gill/programs/token";

const createTokenTx = await buildCreateTokenTransaction({
  feePayer: signer,
  latestBlockhash,
  mint,
  // mintAuthority, // 默认=与 `feePayer` 相同
  metadata: {
    isMutable: true, // 如果 `updateAuthority` 将来可以更改此元数据
    name: "Only Possible On Solana",
    symbol: "OPOS",
    uri: "https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/Climate/metadata.json",
  },
  // updateAuthority, // 默认=与 `feePayer` 相同
  decimals: 2, // 默认=9
  tokenProgram, // 默认=TOKEN_PROGRAM_ADDRESS,也支持 token22
  // 默认计算单元限制设置为优化值,但可以在这里覆盖
  // computeUnitLimit?: number,
  // 从你喜欢的优先费用 API 获取
  // computeUnitPrice?: number, // 未设置默认值
});

铸造代币到目标钱包

构建一个将新代币铸造到 destination 钱包地址的事务(提高代币的总供应量)。

  • 确保设置了 mint 本身使用的正确 tokenProgram
  • 如果 destination 所有者没有为 mint 创建关联代币账户(ata),将自动为他们创建一个
  • 在设置事务中的 amount 时,确保考虑了 mintdecimals

相关指令构建器:getMintTokensInstructions

import { buildMintTokensTransaction } from "gill/programs/token";

const mintTokensTx = await buildMintTokensTransaction({
  feePayer: signer,
  latestBlockhash,
  mint,
  mintAuthority: signer,
  amount: 1000, // 注意:务必考虑 mint 的 `decimals` 值
  // 如果 decimals=2 => 这将铸造 10.00 个代币
  // 如果 decimals=4 => 这将铸造 0.100 个代币
  destination,
  // 使用适用于 `mint` 的正确代币程序
  tokenProgram, // 默认=TOKEN_PROGRAM_ADDRESS
  // 默认计算单元限制设置为优化值,但可以在这里覆盖
  // computeUnitLimit?: number,
  // 从你喜欢的优先费用 API 获取
  // computeUnitPrice?: number, // 未设置默认值
});

转账代币到目标钱包

构建一个将代币从 source 转账到 destination 钱包地址的事务(即从 sourceAtadestinationAta)。

  • 确保设置了 mint 本身使用的正确 tokenProgram
  • 如果 destination 所有者没有为 mint 创建关联代币账户(ata),将自动为他们创建一个
  • 在设置事务中的 amount 时,确保考虑了 mintdecimals

相关指令构建器:getTransferTokensInstructions

import { buildTransferTokensTransaction } from "gill/programs/token";

const transferTokensTx = await buildTransferTokensTransaction({
  feePayer: signer,
  latestBlockhash,
  mint,
  authority: signer,
  // sourceAta, // 默认=从 `authority` 派生
  /**
   * 如果 `sourceAta` 不是从 `authority` 派生的(例如多签钱包),
   * 使用 `getAssociatedTokenAccountAddress()` 手动派生
   */
  amount: 900, // 注意:务必考虑 mint 的 `decimals` 值
  // 如果 decimals=2 => 这将转账 9.00 个代币
  // 如果 decimals=4 => 这将转账 0.090 个代币
  destination: address(...),
  // 使用适用于 `mint` 的正确代币程序
  tokenProgram, // 默认=TOKEN_PROGRAM_ADDRESS
  // 默认计算单元限制设置为优化值,但可以在这里覆盖
  // computeUnitLimit?: number,
  // 从你喜欢的优先费用 API 获取
  // computeUnitPrice?: number, // 未设置默认值
});

调试模式

在 gill 中,你可以启用”调试模式”来自动记录额外信息,这些信息将有助于排查事务问题。

调试模式默认禁用,以最小化应用程序的额外日志。但通过其灵活的调试控制器,你可以从代码运行的最常见位置启用它,包括你的代码本身、Node.js 后端、无服务器函数,甚至 Web 浏览器控制台本身。

gill 已经内置的一些调试日志示例:

如何启用调试模式

要启用调试模式,将以下任何一项设置为 true1

  • process.env.GILL_DEBUG
  • global.__GILL_DEBUG__
  • window.__GILL_DEBUG__(即在 Web 浏览器控制台中)
  • 或手动设置任何调试日志级别(见下文)

要设置应用程序中输出的日志级别,设置以下一项的值(默认:info):

  • process.env.GILL_DEBUG_LEVEL
  • global.__GILL_DEBUG_LEVEL__
  • window.__GILL_DEBUG_LEVEL__(即在 Web 浏览器控制台中)

支持的日志级别(按优先级顺序):

  • debug(最低)
  • info(默认)
  • warn
  • error

自定义调试日志

Gill 还导出了它内部使用的相同调试函数,允许你实现与 Solana 事务相关的自定义调试逻辑,并使用与 gill 相同的控制器。

  • isDebugEnabled() – 检查是否启用了调试模式
  • debug() – 如果达到设置的日志级别,打印调试消息
import { debug, isDebugEnabled } from "gill";

if (isDebugEnabled()) {
  // 你的自定义逻辑
}

// 如果启用了 "info" 或更高级别的日志级别,记录此消息
debug("custom message");

// 如果启用了 "debug" 或更高级别的日志级别,记录此消息
debug("custom message", "debug");

// 如果启用了 "warn" 或更高级别的日志级别,记录此消息
debug("custom message", "warn");

// 如果启用了 "error" 或更高级别的日志级别,记录此消息
debug("custom message", "error");

程序客户端

使用 gill,你还可以导入一些最常用程序的客户端。这些也是完全可树摇的,因此如果你没有在项目中导入它们,它们将在构建时被 JavaScript 打包器(如 Webpack)移除。

导入任何这些程序客户端:

import { ... } from "gill/programs";
import { ... } from "gill/programs/token";

gill 中包含的程序客户端有:

如果现有的客户端没有从 gill/programs 或其子路径导出,你当然可以手动将它们的兼容客户端添加到你的仓库中。

总结

gill 是一个功能强大且易于使用的 Solana 区块链客户端库,为 JavaScript/TypeScript 开发者提供了简洁的 API 和丰富的功能。无论是初学者还是经验丰富的区块链开发者,gill 都能帮助你更高效地构建 Solana 应用程序。

通过本文的介绍,你应该对 gill 的主要功能有了基本了解,包括如何建立连接、进行 RPC 调用、创建和签名交易、使用事务构建器以及调试应用程序。gill 的模块化设计和树摇支持确保了你可以只使用需要的功能,保持应用程序的轻量性。

无论你是要构建去中心化金融应用、NFT 市场还是其他区块链解决方案,gill 都提供了必要的工具和抽象来简化开发过程。