理解 SPL Token:Solana 的通用代币标准
一、什么是 SPL Token?
在以太坊中,ERC-20 是最常见的代币标准。
在 Solana 上,对应的标准是 SPL Token(Solana Program Library Token)。
SPL Token 并不是单个智能合约,而是一套系统标准,
由一个官方 Program(Token Program)负责管理代币的生命周期。
| 项目 | 说明 |
|---|---|
| Program ID | TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA |
| 功能 | 创建代币、铸造、转账、冻结、销毁 |
| 底层结构 | 每个代币都有 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),
因为它能确保每个钱包地址对同一种代币只有一个账户。
三、准备工作
安装依赖:
设置环境:
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 Account | NewInitializeMint2Instruction |
| 创建代币账户 | ATA | associatedtokenaccount.NewCreateInstruction |
| 铸造代币 | Mint To | token.NewMintToInstruction |
| 转账 | Token Transfer | token.NewTransferInstruction |
| 查询余额 | RPC 调用 | GetTokenAccountBalance |
| 事件监听 | gRPC 日志流 | LogsSubscribe |
Share
If this article helped you, please share it with others!
Some content may be outdated