大家好,感谢你们点开这篇文章!如果有兴趣,欢迎关注我的 GitHub,里面有一些我的小项目和代码,水平有限,还请多多指教!
基本概念:什么是 solana 上 spl-token
在 Solana 区块链上,SPL Token 是基于 Solana Program Library(SPL,Solana 程序库)实现的代币标准。它类似于以太坊上的 ERC-20 代币,但专为 Solana 的高性能架构设计,用于创建和管理自定义代币。SPL Token 是 Solana 生态中最常用的代币类型,广泛应用于去中心化金融(DeFi)、NFT、游戏等领域。
SPL Token 是由 Solana 提供的 Token Program (一个预编译的程序,主网的地址为 TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA)管理的代币。可以通过solscan.io/account/Tok... 这个链接查看
它允许用户:
- 创建新的代币(类似 ERC-20 的代币发行)
- 转移代币
- 管理代币的供应量和权限
与以太坊的智能合约模型不同,SPL Token 不需要开发者编写和部署独立的合约,而是通过与 Token Program 交互来实现代币功能。
通过 solana cli 配置环境
要想使用 spl-token cli 这个命令行工具,我们首先需要进行一些前置的开发环境安装:
开发环境快速安装:
在 Mac 和 Linux 上,运行单个命令即可安装所有依赖项:
arduino
curl --proto '=https' --tlsv1.2 -sSfL https://solana-install.solana.workers.dev | bash
Windows 用户:必须首先安装 WSL(请参阅安装依赖项 )。然后在 Ubuntu(Linux)终端中运行上述命令。
安装完毕后,应该会看到如下信息输出:
yaml
Installed Versions:
Rust: rustc 1.85.0 (4d91de4e4 2025-02-17)
Solana CLI: solana-cli 2.1.14 (src:3ad46824; feat:3271415109, client:Agave)
Anchor CLI: anchor-cli 0.30.1
Node.js: v23.8.0
Yarn: 1.22.1
从上面输出的信息可以看到,我们的 Solana CLI 已经安装完成,那么 spl-token cli 这个工具也就可以使用了。
设置网络环境:
solana 的网络环境分为:开发者网络、测试网络、主网 三类,官方的 RPC 地址分别是:
DevNet: api.devnet.solana.com
TestNet: api.testnet.solana.com
MainNet: api.mainnet-beta.solana.com
本篇文章,我们使用的环境是DevNet,因为这个环境比本地网络更加真实,测试代币业容易申请。方便我们演示整个流程。
1、查看当前的环境配置:
arduino
➜ ~ solana config get
ruby
Config File: /Users/louis/.config/solana/cli/config.yml
RPC URL: https://api.mainnet-beta.solana.com
WebSocket URL: wss://api.mainnet-beta.solana.com/ (computed)
Keypair Path: /Users/louis/.config/solana/id.json
Commitment: confirmed
从上面的终端信息可以看出来,默认 solana-cli 工具连接的网络是主网,我们需要切换为DevNet
2、切换网络:
arduino
solana config set --url https://api.devnet.solana.com
ruby
Config File: /Users/louis/.config/solana/cli/config.yml
RPC URL: https://api.devnet.solana.com
WebSocket URL: wss://api.devnet.solana.com/ (computed)
Keypair Path: /Users/louis/.config/solana/id.json
Commitment: confirmed
从上面输出的信息可以看到,我们网络环境,已经切换到了 DevNet。如果你不放心,可以再执行一次:solana config get 命令,看看环境是否已经切换成功。
密钥生成、测试代币领取:
1、生成密钥对:
我们需要一个有 sol 测试代币的账户,来进行整个流程,因为铸造 SPL Token 本质上来说是交易,我们需要用这个账户来付费。
sql
➜ ~ solana-keygen new --force
vbnet
Generating a new keypair
For added security, enter a BIP39 passphrase
NOTE! This passphrase improves security of the recovery seed phrase NOT the
keypair file itself, which is stored as insecure plain text
BIP39 Passphrase (empty for none):
上面交互命令提示我们可以输入一个助记词的短语来增强密钥的安全性,因为我们是测试地址,所以,这里填写空就可以,直接回车。
markdown
Wrote new keypair to /Users/louis/.config/solana/id.json
==============================================================================
pubkey: 71E6vbP88ChQEaq17aF5vkoCvyGeANUYhrn7SLYSpuQx
==============================================================================
Save this seed phrase and your BIP39 passphrase to recover your new keypair:
grass leo meadow mesh apple lucky new zoo install fluid welcome another
==============================================================================
命令行输出了信息告诉你密钥存放的目录,地址是:71E6vbP88ChQEaq17aF5vkoCvyGeANUYhrn7SLYSpuQx,还把相应的助记词打印出来了,这个助记词最好保存一下,以后恢复钱包的时候用的到。
2、查看刚刚生成的公钥地址:
~ solana-keygen pubkey
71E6vbP88ChQEaq17aF5vkoCvyGeANUYhrn7SLYSpuQx
命令行输出刚刚创建的地址,说明我们的操作很成功。我们可以用 solana address 来查看当前钱包的地址:
css
solana address
71E6vbP88ChQEaq17aF5vkoCvyGeANUYhrn7SLYSpuQx
因为我们目前只生成了一个钱包,所以输出的地址和上面的一致。
3、去水龙头申请测试代币:
makefile
➜ ~ solana airdrop 5
Requesting airdrop of 5 SOL
Signature: 4Gh6hphdQNNaDchuC5rwuSR3kLhdZ9q29bj9A3FTXVQhgY3C7GjwkK9Jb9wK9bkPRwa78wG1wuEyRMqXsyLvmGNa
5 SOL
4、查看当前钱包地址余额,看看有没有领取成功
➜ ~ solana balance 71E6vbP88ChQEaq17aF5vkoCvyGeANUYhrn7SLYSpuQx
5 SOL
5、查看账户信息:
vbnet
➜ ~ solana account 71E6vbP88ChQEaq17aF5vkoCvyGeANUYhrn7SLYSpuQx
Public Key: 71E6vbP88ChQEaq17aF5vkoCvyGeANUYhrn7SLYSpuQx
Balance: 5 SOL
Owner: 11111111111111111111111111111111 // 系统地址
Executable: false // 并非可执行的
Rent Epoch: 18446744073709551615
通过 spl-token cli 创建 SPL Token
创建代币:
准备工作做了这么多,终于到了创建代币的步骤:
使用 spl-token -h
和 spl-token create-token -h
查看我们创建 Token 的选项,这里就不全列出来了,读者可自行查看。
spl-token -h
假设我们想要创建一个 6 位小数精度的 Token,命令如下:
makefile
➜ ~ spl-token create-token --decimals 6
Creating token XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ under program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Address: XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ
Decimals: 6
Signature: 5CDK2sMoRkaPLDTnZhGVvpnrewTAACXeosqNGYFHRagWX3mJDbdEPdsurnN4qDVYLtTCkqtBzuYdiqWH8GTy6h3t
如果不传 --decimals 代币精度就是默认的 9。
其中Address 就是我们自己创建的 是 token mint 地址,相当于以太坊的代币合约地址。
执行 mint 操作:
铸造代币,肯定会用到 spl-token mint 命令,我们为 71E6vbP88ChQEaq17aF5vkoCvyGeANUYhrn7SLYSpuQx 这个自己的地址,铸造 1000000 个代币:
spl-token mint XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ 1000000 71E6vbP88ChQEaq17aF5vkoCvyGeANUYhrn7SLYSpuQx
报错了:
javascript
Error: "Account 71E6vbP88ChQEaq17aF5vkoCvyGeANUYhrn7SLYSpuQx is owned by 11111111111111111111111111111111, not configured program id TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
这里需要引申一个知识点:
在 Solana 中,一个账户还需要有专门的 token account
去存相应的代币,并且一个 token account
只存一种 token。所以,我们需要先给我们的账户创建 token 为 XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ 对应的 token account
。
lua
➜ ~ spl-token create-account XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ
Creating account 4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z
Signature: Kg7sg5nut7w7baFNBwQ8FArdoafCw8R2D6WzyoXFzSkeU4jCjPpiPHQL11gvWxbVFTP6jJSrV32Eds6Q8VhBSpL
得到我们这个账户存放这个 token 对应的 token account
: 4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z
重新执行 mint 操作:
spl-token mint XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ 1000000 4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z
- XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ 这是 Mint Account(铸币账户) 的地址,表示你要铸造的代币类型。
- Mint Account 定义了代币的基本信息(如小数位数、总供应量等),并且只有拥有 Mint Authority(铸币权限) 的账户才能对它执行铸造操作。
- 1000000 这是你要铸造的代币数量,这里是 1000000 个单位。注意:实际代币数量需要结合 Mint Account 的小数位数(decimals)来理解。
- 4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z 这是 Token Account(代币账户) 的地址,表示接收新铸造代币的账户。Token Account 是某个用户或程序持有的账户,绑定到特定的 Mint Account,用于存储代币余额。
yaml
Minting 1000000 tokens
Token: XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ
Recipient: 4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z
Signature: 2ACXzvaSg7NuhR3ZLzcmCG7hVaq1LRi9XGtsEwioKinYoFduKScDRY3E9mkqg8S8wqxbn66H4emF1C92hJ2BsfLK
打印出来上面的信息说明 mint 成功了,关键信息解释:
- Recipient: 4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z,指明新铸造的 1000000 个代币被发送到了这个 Token Account。这个账户的余额会增加 1000000 个单位。
- Signature:2ACXzvaSg7NuhR3ZLzcmCG7hVaq1LRi9XGtsEwioKinYoFduKScDRY3E9mkqg8S8wqxbn66H4emF1C92hJ2BsfLK,这是交易的签名(Transaction Signature),Solana 区块链上每笔交易的唯一标识。你可以用这个签名在 Solana 浏览器(如 Solscan 或 Solana Explorer)上查看交易详情,确认它是否成功上链。
mint 操作背后发生了什么?
在链上,这条命令触发了以下过程:
1、权限验证:
- CLI 使用你的钱包(默认通过 solana config get 配置的 keypair)作为签名者。
- 你的钱包地址必须是 XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ 这个 Mint Account 的 Mint Authority,否则操作会失败。
2、 调用 Token Program:
- CLI 向 SPL Token Program(地址:TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA)发送一个 mint_to 指令。
- 指令内容包括:
-
- Mint Account:XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ。
- Token Account:4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z。
- 数量:1000000。
- 签名者:你的钱包地址。
3、 状态更新:
- Mint Account 的总供应量(Total Supply)增加 1000000 个单位。
- Token Account 的余额增加 1000000 个单位。
- 交易记录在 Solana 区块链上,签名作为证明。
执行转账操作:
有了代币之后,我们迫不及待的想要进行转账操作,可以使用 spl-token transfer -h
来查看转账的参数。
xml
USAGE:
spl-token transfer [FLAGS] [OPTIONS] <TOKEN_MINT_ADDRESS> <TOKEN_AMOUNT> <RECIPIENT_WALLET_ADDRESS or RECIPIENT_TOKEN_ACCOUNT_ADDRESS>
我这里准备了我自己之前的地址:BXvnWvd5TQz4CHYHL7953tgMx54CseyyYMynLz14y2dn
这个地址里面是有测试代币的,因为我之前已经领过。我给这个地址转 100 个代币。
csharp
# --from 后面跟上 token account
spl-token transfer XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ 100 BXvnWvd5TQz4CHYHL7953tgMx54CseyyYMynLz14y2dn --from 4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z
发现继续报错:
yaml
Transfer 100 tokens
Sender: 4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z
Recipient: BXvnWvd5TQz4CHYHL7953tgMx54CseyyYMynLz14y2dn
Recipient associated token account: ASmknEopi9KGrJH1UfYzi4zrhP9jTTRWHQvDo9UAJR48
Error: "Error: Recipient's associated token account does not exist. Add `--fund-recipient` to fund their account"
因为我们填的收款地址是钱包地址,而不是对方的 token account
。根据提示,添加 --fund-recipient
。
css
# --from 后面跟上 token account
spl-token transfer XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ 100 BXvnWvd5TQz4CHYHL7953tgMx54CseyyYMynLz14y2dn --from 4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z --fund-recipient
查看执行情况:
yaml
Transfer 100 tokens
Sender: 4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z
Recipient: BXvnWvd5TQz4CHYHL7953tgMx54CseyyYMynLz14y2dn
Recipient associated token account: ASmknEopi9KGrJH1UfYzi4zrhP9jTTRWHQvDo9UAJR48
Funding recipient: ASmknEopi9KGrJH1UfYzi4zrhP9jTTRWHQvDo9UAJR48
Signature: 4p5CokovNCLf7anFtSM1ZBNXPyzrNa1e1TQFzzj1NSA1zzd66tksBEbcsVb9zpgeSHB81znizyBy9qnKw9oV9T3d
关键信息解释:
- 4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z,发送者的 Token Account 地址。这个账户的余额会减少 100 个单位。
- BXvnWvd5TQz4CHYHL7953tgMx54CseyyYMynLz14y2dn 这是 接收者的钱包地址 (Recipient)。在 Solana 上,接收者通常是一个钱包公钥,CLI 会自动查找或创建它的 Associated Token Account(ATA) 来接收代币。
- --from 4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z 指定发送者的 Token Account(代币账户)这个账户持有你要转移的代币(之前你铸造了 1000000 个单位到这里)。
- --fund-recipient,这是一个可选参数,指示 CLI 如果接收者的 Associated Token Account 不存在,则自动创建它,并支付创建所需的租金(rent)费用,租金由你的钱包地址(默认 keypair)支付。
转账操作背后发生了什么?
1、 权限验证:
- CLI 使用你的钱包(默认 keypair)作为签名者。
- 你的钱包地址必须是 4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z 这个 Token Account 的 Owner(所有者) ,否则无法转移代币。
2、 检查或创建接收者 ATA:
- CLI 检查接收者钱包地址(BXvnWvd5TQz4CHYHL7953tgMx54CseyyYMynLz14y2dn)是否已有与 Mint Account(XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ)关联的 ATA。
- 如果没有(由于用了 --fund-recipient),CLI 创建了 ATA(ASmknEopi9KGrJH1UfYzi4zrhP9jTTRWHQvDo9UAJR48),并支付租金。
3、 调用 Token Program:
- CLI 向 SPL Token Program(地址:TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA)发送一个 transfer 指令。
- 指令内容包括:
-
- Source(发送者):4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z。
- Destination(接收者):ASmknEopi9KGrJH1UfYzi4zrhP9jTTRWHQvDo9UAJR48。
- 数量:100。
- 签名者:你的钱包地址。
4、 状态更新:
- 发送者 Token Account 的余额减少 100 个单位。
- 接收者 ATA 的余额增加 100 个单位。
- 交易记录在链上,签名作为证明。
查看代币余额:
spl-token balance 这个命令有两种用法,
1、查询默认关联账户的余额:
xml
spl-token balance <TOKEN_MINT_ADDRESS>
- 这里 <TOKEN_MINT_ADDRESS> 是 Mint Account 的地址( XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ)。
- CLI 会查找调用者的默认 Associated Token Account(ATA,关联代币账户) ,并返回该账户中指定代币的余额。
- 前提是钱包地址(默认 keypair)已经为这个 Mint Account 创建了 ATA。
2、查询特定的 Token Account 的余额:
css
spl-token balance --address <TOKEN_ACCOUNT_ADDRESS>
- 这里 <TOKEN_ACCOUNT_ADDRESS> 是具体的 Token Account 地址( 4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z)。
- 使用 --address 参数明确指定要查询的 Token Account,而不是依赖默认的 ATA。
css
➜ ~ spl-token balance --address 4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z
999900
➜ ~ spl-token balance XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ
999900
从上面的命令可以看出,这两种操作输出的数量都是一样的。
我们可以查看下 BXvnWvd5TQz4CHYHL7953tgMx54CseyyYMynLz14y2dn 这个地址的代币余额。
css
spl-token balance --address ASmknEopi9KGrJH1UfYzi4zrhP9jTTRWHQvDo9UAJR48
100
我们的 BXvnWvd5TQz4CHYHL7953tgMx54CseyyYMynLz14y2dn 的 ATA 账户已经成功收到了 100 个单位的代币。
什么是 ATA 账户?
了解过以太坊的朋友会发现,solana 查询代币余额的方式很不一样,并且还出现了一个 ATA 账户的概念,为什么不能通过 BXvnWvd5TQz4CHYHL7953tgMx54CseyyYMynLz14y2dn 这个地址查询呢?
以太坊的 (ERC20) :
代币存储方式:
- 在以太坊上,ERC-20 代币的余额是存储在智能合约的状态中的。
- 每个 ERC-20 代币都有自己的智能合约,合约内部维护一个映射(如 mapping(address => uint256) balances),记录每个地址的代币余额。
查询余额:
- 你可以通过调用合约的 balanceOf(address) 函数,直接传入钱包地址(如 0x123...),查询该地址的代币余额。
- 例如:tokenContract.balanceOf("0x123...") 返回余额。
Solana(SPL Token)
代币存储方式:
- Solana 使用的是 账户模型(Account Model) ,而不是以太坊的智能合约模型。
- SPL Token 的余额不是存储在某个全局合约中,而是存储在独立的 Token Account(代币账户) 中。
- 每个 Token Account 是一个独立的链上账户,绑定到特定的 Mint Account(铸币账户) ,记录某个用户或程序持有的该代币余额。
查询余额:
- 你需要知道具体的 Token Account 地址(例如 4sxAMyvRJPLPQ8PBbv8DRP3u1KvnfWe4GsWZ5653yx9z 或 ATA ASmknEopi9KGrJH1UfYzi4zrhP9jTTRWHQvDo9UAJR48),然后查询这个账户的数据。
- 例如:spl-token balance --address <TOKEN_ACCOUNT_ADDRESS>。
这引入了 ATA(关联代币账户) 的概念。
ATA(关联代币账户)
ATA 是 Solana 生态中的一种标准化 Token Account,它的地址是通过 确定性推导 从以下两个参数生成的:
- Owner:钱包地址(如 BXvnWvd5TQz4CHYHL7953tgMx54CseyyYMynLz14y2dn)。
- Mint:代币的 Mint Account(如 XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ)。
ATA 的地址是唯一的,由程序(如 SPL Associated Token Account Program)计算得出。
生成方式:
- ATA 使用了 Program Derived Address(PDA) 机制,通过哈希算法(SHA-256)结合 Owner、Mint 和 SPL Token Program 的地址生成。
- 示例:ASmknEopi9KGrJH1UfYzi4zrhP9jTTRWHQvDo9UAJR48 是 BXvnWvd5TQz4CHYHL7953tgMx54CseyyYMynLz14y2dn 对于代币 XoHuoMGhJh8X7yF1BLCvYyK4huNvSTSXJSK4jZrCWmZ 的 ATA。
作用:
- 简化操作:ATA 提供了一个标准化的账户,方便用户和程序快速定位某个钱包对特定代币的默认持有账户。
- 生态一致性:钱包(如 Phantom)和 DApp 默认使用 ATA 来存储和转移代币,避免用户手动创建和管理多个 Token Account。