Derick
1152 words
6 minutes
理解 SPL Token:Solana 的通用代币标准

一、什么是 SPL Token?#

在以太坊中,ERC-20 是最常见的代币标准。

在 Solana 上,对应的标准是 SPL Token(Solana Program Library Token)

SPL Token 并不是单个智能合约,而是一套系统标准,

由一个官方 Program(Token Program)负责管理代币的生命周期。

项目说明
Program IDTokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
功能创建代币、铸造、转账、冻结、销毁
底层结构每个代币都有 Mint Account;每个持有人都有 Token Account
存储形式所有代币余额都存储在独立的账户数据中,而非 Program 内部

简单说:

  • Mint Account 是代币的定义(类似 ERC-20 合约)。
  • Token Account 是用户的余额存储位置。

二、代币账户的结构#

每种 SPL Token 都有一组对应的账户结构:

类型说明
Mint Account定义代币(总发行量、精度、小数位等)
Token Account存储某个用户的代币余额
Associated Token Account (ATA)系统推荐的 Token Account 生成方式,自动与用户钱包地址绑定

在实际开发中,我们几乎总是使用 ATA(Associated Token Account),

因为它能确保每个钱包地址对同一种代币只有一个账户。


三、准备工作#

安装依赖:

go get github.com/gagliardetto/[email protected]
go get github.com/gagliardetto/[email protected]

设置环境:

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/gagliardetto/solana-go"
	"github.com/gagliardetto/solana-go/programs/associated-token-account"
	"github.com/gagliardetto/solana-go/programs/system"
	"github.com/gagliardetto/solana-go/programs/token"
	"github.com/gagliardetto/solana-go/rpc"
)

四、创建新代币(Mint)#

以下代码展示如何在 Devnet 上创建一个新代币(Mint Account)。

func main() {
	ctx := context.Background()
	client := rpc.New(rpc.DevNet_RPC)

	// 加载钱包(即代币创建者)
	creator, err := solana.PrivateKeyFromSolanaKeygenFile("/Users/alan/.config/solana/devnet.json")
	if err != nil {
		log.Fatalf("加载钱包失败: %v", err)
	}

	// 创建 Mint 账户(新代币定义)
	mint := solana.NewWallet()

	// 获取区块哈希
	recent, err := client.GetLatestBlockhash(ctx, rpc.CommitmentFinalized)
	if err != nil {
		log.Fatalf("获取哈希失败: %v", err)
	}

	//  构建交易
	tx, err := solana.NewTransaction(
		[]solana.Instruction{
			// 创建账户
			system.NewCreateAccountInstruction(
				2_039_280,                  // 租金
				token.MintAccountSize,      // 账户大小
				token.ProgramID,            // 所属 Program
				creator.PublicKey(),
				mint.PublicKey(),
			).Build(),

			// 初始化 Mint
			token.NewInitializeMint2Instruction(
				9,                  // 精度(小数位数)
				creator.PublicKey(), // 铸造权限
				nil,                 // 冻结权限(可选)
				mint.PublicKey(),    // Mint 地址
				token.ProgramID,
			).Build(),
		},
		recent.Value.Blockhash,
		creator.PublicKey(),
	)
	if err != nil {
		log.Fatalf("构建交易失败: %v", err)
	}

	// 签名并发送
	_, err = tx.Sign(func(pub solana.PublicKey) *solana.PrivateKey {
		switch {
		case pub.Equals(creator.PublicKey()):
			return &creator
		case pub.Equals(mint.PublicKey()):
			return &mint.PrivateKey
		default:
			return nil
		}
	})
	if err != nil {
		log.Fatalf("签名失败: %v", err)
	}

	sig, err := client.SendTransaction(ctx, tx)
	if err != nil {
		log.Fatalf("发送失败: %v", err)
	}

	fmt.Println("成功创建代币 Mint:", mint.PublicKey())
	fmt.Println("交易签名:", sig)
}

运行后,你将拥有一个新的代币定义(Mint Account)。

可以在 Solana Explorer 中搜索 mint.PublicKey() 查看详情。


五、创建持有人账户(Associated Token Account)#

每个用户想要持有该代币,都需要一个 Token Account。

使用 Associated Token Program 可以自动生成:

ata, _, err := associatedtokenaccount.GetAssociatedTokenAddress(
	creator.PublicKey(),  // 用户钱包地址
	mint.PublicKey(),     // 对应代币的 Mint 地址
)
if err != nil {
	log.Fatalf("生成 ATA 失败: %v", err)
}

instruction := associatedtokenaccount.NewCreateInstruction(
	creator.PublicKey(),  // 资助账户
	ata,                  // 目标 ATA
	creator.PublicKey(),  // 拥有者
	mint.PublicKey(),     // 代币
).Build()

该指令会自动创建一个符合标准的代币账户。

注意: ATA 的地址是可预测的,因此无需存数据库。

你随时可以通过同样方法计算出来。


六、铸造代币(Mint To)#

一旦 Mint 创建完成,就可以给任意账户“铸造”代币:

mintInstruction := token.NewMintToInstruction(
	mint.PublicKey(),     // 代币 Mint 地址
	ata,                  // 接收账户
	creator.PublicKey(),  // 铸造权限
	1_000_000_000,        // 数量(考虑精度)
).Build(

再组合交易发送即可。


七、查询余额#

查询某个 Token Account 的余额,只需调用 RPC:

balance, err := client.GetTokenAccountBalance(ctx, ata, rpc.CommitmentFinalized)
if err != nil {
	log.Fatalf("查询余额失败: %v", err)
}
fmt.Println(" 当前余额:", balance.Value.UiAmountString)

八、执行转账#

Token Program 的 Transfer 指令允许用户从一个 Token Account 向另一个账户发送代币:

instruction := token.NewTransferInstruction(
	amount,               // 转账数量
	senderATA,            // 发送方代币账户
	receiverATA,          // 接收方代币账户
	sender.PublicKey(),   // 授权签名者
	nil,                  // 多签(可选)
	token.ProgramID,
).Build()

再像前面那样签名并广播。

这与 System Program 的 SOL 转账几乎一致,只是多了 Token Account 参与。


九、事件与监听(gRPC)#

在生产环境中,我们通常需要监听某个 Mint 的交易事件。

Solana 支持通过 gRPC 订阅日志流

stream, err := client.LogsSubscribe(ctx, rpc.LogsSubscribeFilterMentions{
    Mentions: []solana.PublicKey{token.ProgramID},
}, rpc.CommitmentFinalized)
if err != nil {
	log.Fatalf("订阅失败: %v", err)
}

for {
	msg, err := stream.Recv()
	if err != nil {
		log.Fatal("接收失败:", err)
	}
	fmt.Println(" Token Program 日志:", msg.Value.Logs)
}

通过这种方式,可以捕获任意代币的转账、铸造等链上事件。


十、总结#

内容概念示例函数
创建代币Mint AccountNewInitializeMint2Instruction
创建代币账户ATAassociatedtokenaccount.NewCreateInstruction
铸造代币Mint Totoken.NewMintToInstruction
转账Token Transfertoken.NewTransferInstruction
查询余额RPC 调用GetTokenAccountBalance
事件监听gRPC 日志流LogsSubscribe
理解 SPL Token:Solana 的通用代币标准
https://blog.ithuo.net/posts/solana-spl-token-introduction/
Author
Derick
Published at
2024-12-17