Solana开发实战:使用@solana/kit(v2)发行SPL代币全流程Solana的开发工具正在经历一次重要的进化。随着@solana/web3.js2.x版本正式更名为@solana/kit,官方SDK迎来了更轻量、更模块化、且全面拥抱函数式编程的新时代。对于开
Solana 的开发工具正在经历一次重要的进化。
随着 @solana/web3.js 2.x 版本正式更名为 @solana/kit,官方 SDK 迎来了更轻量、更模块化、且全面拥抱函数式编程的新时代。对于开发者而言,这意味着代码将变得更具可组合性,同时能更好地利用 Tree-shaking 优化包体积。
但新技术总需要一段适应期。新版的 Rpc 怎么建?交易流水线 pipe 怎么写?如何像以前一样轻松地发币?
本文将抛开繁杂的理论,直接进入代码实操模式。我们将基于本地测试网络(Surfnet),手把手带你跑通从环境初始化、加载签名者、到完成一笔“原子化铸币”交易的全过程,最后教你如何读取并验证链上数据。
准备好了吗?让我们开始构建。
使用 @solana/kit 铸造一个 SPL Token
Solana Kit 这是用于构建适用于 Node、Web 和 React Native 的 Solana 应用程序的 JavaScript SDK。@solana/web3.js 的 2.x 版本重命名为 @solana/kit。
pnpm install --save @solana/kit
bun add @solana-program/system \
@solana-program/memo \
@solana-program/token \
@solana-program/compute-budget
connect.ts 文件import {
createSolanaRpc,
createSolanaRpcSubscriptions,
sendAndConfirmTransactionFactory,
} from '@solana/kit'
// 1. 设置 RPC 连接(指向你的本地 Surfpool)
const rpc = createSolanaRpc('http://127.0.0.1:8899') //
// 2. 设置订阅服务(用于实时确认交易)
const rpcSubscriptions = createSolanaRpcSubscriptions('ws://127.0.0.1:8900') //
// 3. 创建发送并确认交易的工具函数
const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions }) //
console.log("🚀 Solana Kit 已就绪,连接至本地 Surfnet!")
// 快速测试:获取当前 Slot
const slot = await rpc.getSlot().send()
console.log(`当前本地 Slot: ${slot}`)
/**
* Code/Solana/solana_forge via 🍞 v1.2.17
➜ bun run src/solanakit/connect.ts
🚀 Solana Kit 已就绪,连接至本地 Surfnet!
当前本地 Slot: 395783430
*/
这段代码使用 Solana Kit 初始化了与本地开发环境(如 Surfpool 或本地验证器)的通信链路:首先通过 createSolanaRpc 建立 HTTP 连接以执行基础查询,接着利用 createSolanaRpcSubscriptions 开启 WebSocket 监听以实时获取交易状态,最后通过 sendAndConfirmTransactionFactory 将两者整合为一个能够自动发送并等待网络确认的工具函数,从而实现对链上数据(如 Slot 高度)的异步交互。
Code/Solana/solana_forge via 🍞 v1.2.17
➜ bun run src/solanakit/connect.ts
🚀 Solana Kit 已就绪,连接至本地 Surfnet!
当前本地 Slot: 395783430
这段运行结果展示了通过 Bun 运行时成功执行了连接脚本,证实了 Solana Kit 客户端 已与本地 Surfnet 节点建立双向通信:它不仅完成了 RPC 实例的初始化,还通过成功获取并打印当前的 Slot(插槽高度),验证了程序具备从链上读取实时数据以及发送指令的能力。
signer.ts 文件import {
createKeyPairFromBytes,
createSignerFromKeyPair,
address
} from '@solana/kit'
import { readFileSync } from 'node:fs'
export async function getLocalSigner() {
// 1. 读取 64 字节的 id.json
const WALLET_PATH = "/Users/qiaopengjun/.config/solana/id.json"
const secretKeyArray = JSON.parse(readFileSync(WALLET_PATH, 'utf-8'))
const secretKeyBytes = new Uint8Array(secretKeyArray)
// 2. 将字节转为 CryptoKeyPair
const keyPair = await createKeyPairFromBytes(secretKeyBytes)
// 3. 关键一步:将 KeyPair 包装成 Signer
const signer = createSignerFromKeyPair(keyPair)
// 现在 signer.address 就有值了!
return signer
}
// 执行并打印
const signer = await getLocalSigner()
console.log(`✅ Kit 签名者已就绪: ${signer.address}`)
这段代码通过 node:fs 读取本地 Solana CLI 默认生成的 id.json 私钥文件,并利用 Solana Kit 的 createKeyPairFromBytes 将原始字节数组转换为非对称加密密钥对,最后通过 createSignerFromKeyPair 将其封装为具备自动签名能力的 Signer 对象,从而实现将本地开发钱包安全地导入到 Web3 应用中以进行交易授权。
Code/Solana/solana_forge via 🍞 v1.2.17
➜ bun run src/solanakit/signer.ts
✅ Kit 签名者已就绪: 6MZDRo5v8K2NfdohdD76QNpSgk3GH3Aup53BeMaRAEpd
该运行结果表明脚本已成功通过 Bun 运行时解析并加载了本地文件系统中的私钥,利用 Solana Kit 将其转换为合法的加密签名者对象,并正确派生出了对应的公共地址(6MZD...),这意味着该身份已准备就绪,可以代表该钱包在后续的流水线中对任何链上交易进行授权签名。
client.ts 文件import {
createSolanaRpc,
createSolanaRpcSubscriptions,
} from '@solana/kit'
import type {
Rpc,
RpcSubscriptions,
SolanaRpcApi,
SolanaRpcSubscriptionsApi,
TransactionSigner,
MessageSigner
} from '@solana/kit'
import { getLocalSigner } from './signer'
// 定义 Client 类型
export type Client = {
rpc: Rpc<SolanaRpcApi>
rpcSubscriptions: RpcSubscriptions<SolanaRpcSubscriptionsApi>
wallet: TransactionSigner & MessageSigner
}
let client: Client | undefined
export async function getClient(): Promise<Client> {
if (!client) {
const rpc = createSolanaRpc('http://127.0.0.1:8899')
const rpcSubscriptions = createSolanaRpcSubscriptions('ws://127.0.0.1:8900')
const wallet = await getLocalSigner()
client = { rpc, rpcSubscriptions, wallet }
}
return client
}
这段代码采用 单例模式 封装了 Solana 客户端的初始化逻辑,通过定义 Client 类型将远程过程调用(RPC)、WebSocket 订阅服务以及本地签名钱包(Wallet)整合在一起,并利用 getClient 异步函数确保在整个应用程序生命周期内只创建一次连接实例,从而为其他模块提供了一个高效、统一且类型安全的链上交互入口。
/** Mint an SPL Token
*
*
* Goal:
* Mint an SPL token in a single transaction using @solana/kit library.
*
* Objectives:
* 1. Create an SPL mint account.
* 2. Initialize the mint with 6 decimals and your public key (feePayer) as the mint and freeze authorities.
* 3. Create an associated token account for your public key (feePayer) to hold the minted tokens.
* 4. Mint 21,000,000 tokens to your associated token account.
* 5. Sign and send the transaction.
*/
import {
appendTransactionMessageInstructions,
assertIsSendableTransaction,
assertIsTransactionWithBlockhashLifetime,
createTransactionMessage,
generateKeyPairSigner,
getSignatureFromTransaction,
pipe,
sendAndConfirmTransactionFactory,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners
} from '@solana/kit'
import {
findAssociatedTokenPda,
getCreateAssociatedTokenInstruction,
getCreateAssociatedTokenInstructionAsync,
getInitializeMintInstruction,
getMintSize,
getMintToInstruction,
TOKEN_PROGRAM_ADDRESS
} from '@solana-program/token'
import { getCreateAccountInstruction } from '@solana-program/system'
import { getClient } from "./client"
async function main() {
try {
const { rpc, rpcSubscriptions, wallet } = await getClient()
const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })
// --- 准备阶段 ---
const decimals = 6
const mintAmount = 21_000_000n * (10n ** BigInt(decimals))
const mintSize = getMintSize()
const [mintSigner, { value: latestBlockhash }, mintRent] = await Promise.all([
generateKeyPairSigner(),
rpc.getLatestBlockhash().send(),
rpc.getMinimumBalanceForRentExemption(BigInt(mintSize)).send(),
])
// 计算 ATA 地址 (PDA)
const [ataAddress] = await findAssociatedTokenPda({
mint: mintSigner.address,
owner: wallet.address,
tokenProgram: TOKEN_PROGRAM_ADDRESS,
})
console.log(`✅ 加载钱包: ${wallet.address}`)
console.log(`🛠️ 创建 Mint: ${mintSigner.address}`)
console.log(`📦 ATA 地址: ${ataAddress}`)
const createAtaIx = await getCreateAssociatedTokenInstructionAsync({
payer: wallet,
mint: mintSigner.address,
owner: wallet.address,
tokenProgram: TOKEN_PROGRAM_ADDRESS,
})
// --- 构建流水线交易 (Objective 1-5) ---
const transactionMessage = pipe(
createTransactionMessage({ version: 'legacy' }),
(tx) => setTransactionMessageFeePayerSigner(wallet, tx),
(tx)... 如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!