一、什么是 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),
因为它能确保每个钱包地址对同一种代币只有一个账户。
三、准备工作
安装依赖:
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 Account | NewInitializeMint2Instruction |
| 创建代币账户 | ATA | associatedtokenaccount.NewCreateInstruction |
| 铸造代币 | Mint To | token.NewMintToInstruction |
| 转账 | Token Transfer | token.NewTransferInstruction |
| 查询余额 | RPC 调用 | GetTokenAccountBalance |
| 事件监听 | gRPC 日志流 | LogsSubscribe |
