跟我一起从0开始学习Solana合约开发,一起实操,一起做项目。这是一个系列文章,系统地记录了我的学习笔记。
熟悉以太坊的同学知道,创建 ERC-20
代币需要编写一个继承自 OpenZeppelin 库(@openzeppelin/contracts
)的 ERC-20 合约,并定义代币的名称、符号、供应量等。然而,在 Solana 中创建代币和以太坊有很大的不同。
今天我们通过实际操作创建和发行一个 SPL 标准代币,深入理解 Solana 的代币管理,并了解其核心概念。
在 Solana 上,创建和管理 SPL 代币的过程相对简化,因为 Solana 提供了一个现成的 SPL Token Program
,开发者不需要编写复杂的智能合约代码,只需使用现成的命令行工具或库。
为了使用 SPL Token Program
创建 SPL 代币,我们需要使用 Solana 提供的命令行工具 spl-token
,现在来安装这个工具。
注意:需要你提前安装好
rust
和solana
环境,如果你还没有安装,请参考本系列文章第一篇。
运行以下命令:
cargo install spl-token-cli
使用 spl-token
命令行工具创建一个新的代币。创建代币时,Solana 会为你生成一个 铸币账户(Mint Account)
,这个账户存储代币的必要信息(例如供应量、授权账户等)。
运行以下命令,创建一个新的代币:
spl-token create-token
输出如下:
Creating token G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3 under program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Address: G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3
Decimals: 9
Signature: 4JTsCKe9RkN8RPjPpU9piiS9GfVFm4gqgL3KwszZcdWpRzgwg7LtyUKwdD517Xcdi3WumBdRBBLQCCsDDTwZLfW
生成的 Address(G8Z8Ar...
)是代币的铸币账户地址(Mint Address),用于存储代币信息,如供应量和授权账户,代表了一种代币。
新创建的代币初始供应量为 0,可通过以下命令验证:
spl-token supply G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3
# 输出:0
在铸造代币之前,需要先创建一个账户,来持有新代币的余额。
在为代币铸造供应量之前,需要创建一个 Token Account
,用于存储代币余额。每个 Token Account 关联特定代币(Mint)。运行以下命令创建账户:
spl-token create-account G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3
输出如下:
Creating account Dx31MHuMRdf14mr73c6Bn7oKCVMP8nqvv5EJX7m8pES7
Signature: 4FUTpTBmMZ1mmnWngVZqMU3xyF3XQ2S4CYLmW7bms6ba8ALe4a4s4Dvrv4NdeKPSXAhTaDRs4TV9EtiqwL2cgucX
生成的地址 Dx31MH...
就是代币账户地址,用于存储上文创建的代币(即铸币账户)
的余额。现在,这个代币账户的余额是 0
。
铸造代币的过程会增加铸币账户的供应量(Supply)
和 代币账户的余额(Balance)
。以下是铸造命令格式:
spl-token mint <mint_address> <amount> <token_account_address>
例如,为刚创建的代币账户铸造 100 个代币:
spl-token mint G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3 100 Dx31MHuMRdf14mr73c6Bn7oKCVMP8nqvv5EJX7m8pES7
输出如下:
Minting 100 tokens
Token: G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3
Recipient: Dx31MHuMRdf14mr73c6Bn7oKCVMP8nqvv5EJX7m8pES7
Signature: 3eetz3xP3nNdTqNzGPrWcvroYXJUHJSm53xHgNcfSvBA8Hk4Qmz6y7QPVcgw3ABWBu8a5fnB7moWYgBhj3DLbiSU
此时可通过以下命令查看铸币账户供应量和代币账户余额:
spl-token supply G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3 # 输出:100
spl-token balance G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3 # 输出:100
spl-token display Dx31MHuMRdf14mr73c6Bn7oKCVMP8nqvv5EJX7m8pES7
输出如下:
SPL Token Account
Address: Dx31MHuMRdf14mr73c6Bn7oKCVMP8nqvv5EJX7m8pES7
Program: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Balance: 100
Decimals: 9
Mint: G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3
Owner: EZVWcdQu3qVMEhTikTLyQZoBaR9KXWwyxhpiTugRe5Rk
State: Initialized
Delegation: (not set)
Close authority: (not set)
spl-token display G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3
输出如下:
SPL Token Mint
Address: G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3
Program: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Supply: 100000000000
Decimals: 9
Mint authority: EZVWcdQu3qVMEhTikTLyQZoBaR9KXWwyxhpiTugRe5Rk
Freeze authority: (not set)
除了使用 spl-token display <mint_address>
查看代币信息,你还可以打开 Devnet 的区块浏览器查看我们刚刚创建的代币,如下网址:
<https://explorer.solana.com/address/G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3?cluster=devnet>
spl-token accounts
运行命令,输出我持有的全部代币:
Token Balance
-----------------------------------------------------
G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3 100
使用 spl-token
工具可以进行代币的转账、查询余额等各种操作。
我们创建一个新钱包,来测试钱包之间的代币转账。使用 solana-keygen
命令生成一个新的钱包地址:
# 这里指定 id-01.json 是为了和默认的 id.json 进行区分
solana-keygen new -o ~/.config/solana/id-01.json
输出示例:
pubkey: GEpoPXXoyWi3TZeZmvFgheoej2kdBMGXV7tDetUWKGtu
现在,我们向这个新钱包转账:
spl-token transfer G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3 50 GEpoPXXoyWi3TZeZmvFgheoej2kdBMGXV7tDetUWKGtu
输出报错了,内容如下:
Transfer 50 tokens
Sender: Dx31MHuMRdf14mr73c6Bn7oKCVMP8nqvv5EJX7m8pES7
Recipient: GEpoPXXoyWi3TZeZmvFgheoej2kdBMGXV7tDetUWKGtu
Error: "Error: The recipient address is not funded. Add `--allow-unfunded-recipient` to complete the transfer."
这个错误是因为接收地址是我们刚刚创建的,还没有代币账户来存储余额,通常,有两种方法解决:
首先,切换默认钱包至新创建的钱包:
# 设置钱包
solana config set --keypair ~/.config/solana/id-01.json
# 查看当前配置钱包
solana address
# 查看当前所有配置信息
solana config get
然后,创建代币账户:
spl-token create-account G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3
之后,你就可以切换回之前的钱包,给这个新钱包转账了。
这种方法需要发送者资助一些 SOL 给接收者,因为创建代币账户是需要支付租金的。代币账户虽然是接收方的,但创建时需要有人支付这笔费用。发送方选择为接收方代付这部分费用,因此称为“fund”。
运行以下命令:
# --fund-recipient 表示资助接收者 SOL 用于支付代币账户租金
# --allow-unfunded-recipient 表示允许自动为接收者创建代币账户
spl-token transfer --fund-recipient G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3 50 GEpoPXXoyWi3TZeZmvFgheoej2kdBMGXV7tDetUWKGtu --allow-unfunded-recipient
输出内容如下:
Transfer 50 tokens
Sender: Dx31MHuMRdf14mr73c6Bn7oKCVMP8nqvv5EJX7m8pES7
Recipient: GEpoPXXoyWi3TZeZmvFgheoej2kdBMGXV7tDetUWKGtu
Recipient associated token account: CW3FRMnpBAorPADHtaYLW4EonGDY75JNjSCBDtg4PZb6
Funding recipient: CW3FRMnpBAorPADHtaYLW4EonGDY75JNjSCBDtg4PZb6
Signature: 2bBQK9Tf7y55s9ELHTxfqJgaDqEE9Q8BTHZ9sdLmGmFktSzexmLUe5DGEEvtrWTZSY4K35nxJrkkBS2vRBgZsjik
通过输出信息可以看到,已经自动为接收者创建了一个代币账户,并成功转账了 50 个代币。
除了给钱包(已关联代币账户)转账,也可以直接给已存在的代币账户转账。
例如,向刚刚新创建的代币账户转账:
spl-token transfer G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3 50 CW3FRMnpBAorPADHtaYLW4EonGDY75JNjSCBDtg4PZb6
输出如下:
Transfer 50 tokens
Sender: Dx31MHuMRdf14mr73c6Bn7oKCVMP8nqvv5EJX7m8pES7
Recipient: CW3FRMnpBAorPADHtaYLW4EonGDY75JNjSCBDtg4PZb6
Signature: 4PKH4QUemGJeM2N6bxV36tTa1gUnd2UBMYRXENm41EEgzBVChGzpY7uKr7KMzbE11iAEERECMJXsXfEdAskjPN1f
现在,我们查看这个代币账户的余额看看效果,运行以下命令:
spl-token display CW3FRMnpBAorPADHtaYLW4EonGDY75JNjSCBDtg4PZb6
输出如下:
SPL Token Account
Address: CW3FRMnpBAorPADHtaYLW4EonGDY75JNjSCBDtg4PZb6
Program: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Balance: 100
Decimals: 9
Mint: G8Z8ArrhUnNYZ1ZjvoNpECSR67Ug5RTbYzewvgcUp3h3
Owner: GEpoPXXoyWi3TZeZmvFgheoej2kdBMGXV7tDetUWKGtu
State: Initialized
Delegation: (not set)
Close authority: (not set)
可以看到,余额是 100 个代币,符合预期,因为第一次向钱包转账了 50 个,第二次直接向代币账户转账了 50 个。
我们的代币还没有元数据,比如 name
,symbol
,uri
等,由于我们的代币是由 Token Program (经典 SPL Token Program) 创建的,要想添加元数据,需要使用 Metaplex Token Metadata Program 才能实现,而官方已经推荐使用新的代币标准( 即 Token-2022 Program )。
Token-2022 是对 经典 SPL Token Program 的增强版本,支持更丰富的功能,可以直接为代币设置名称(name)
、符号(symbol)
和资源定位符(URI)
等。
因此,我们重新创建一个 Token-2022 Program
的代币。
# -p 指定 Token-2022 Program 的地址
# --enable-metadata 启用元数据
spl-token create-token -p TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb --enable-metadata
输出为:
Creating token AQtCRnZtcD4B9Z4ovd9qEhe1cJo5EvDNB2rtq3tKeemD under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
To initialize metadata inside the mint, please run `spl-token initialize-metadata AQtCRnZtcD4B9Z4ovd9qEhe1cJo5EvDNB2rtq3tKeemD <YOUR_TOKEN_NAME> <YOUR_TOKEN_SYMBOL> <YOUR_TOKEN_URI>`, and sign with the mint authority.
Address: AQtCRnZtcD4B9Z4ovd9qEhe1cJo5EvDNB2rtq3tKeemD
Decimals: 9
Signature: 5aE4od7ks3NNckwt3oBXDo9UFfHfLyMLTj4CsYyM8YJK1toDypkvAXTvp4nwQoa62vA49MgrhapUywzKg8cDUYGo
根据提示命令,我们为新代币添加 name
和 symbol
。
spl-token initialize-metadata AQtCRnZtcD4B9Z4ovd9qEhe1cJo5EvDNB2rtq3tKeemD "My SPL Token" "MST" ""
初始化元数据之后,查看代币信息,运行命令:
spl-token display AQtCRnZtcD4B9Z4ovd9qEhe1cJo5EvDNB2rtq3tKeemD
输出如下:
SPL Token Mint
Address: AQtCRnZtcD4B9Z4ovd9qEhe1cJo5EvDNB2rtq3tKeemD
Program: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
Supply: 0
Decimals: 9
Mint authority: EZVWcdQu3qVMEhTikTLyQZoBaR9KXWwyxhpiTugRe5Rk
Freeze authority: (not set)
Extensions
Metadata Pointer:
Authority: EZVWcdQu3qVMEhTikTLyQZoBaR9KXWwyxhpiTugRe5Rk
Metadata address: AQtCRnZtcD4B9Z4ovd9qEhe1cJo5EvDNB2rtq3tKeemD
Metadata:
Update Authority: EZVWcdQu3qVMEhTikTLyQZoBaR9KXWwyxhpiTugRe5Rk
Mint: AQtCRnZtcD4B9Z4ovd9qEhe1cJo5EvDNB2rtq3tKeemD
Name: My SPL Token
Symbol: MST
URI: (not set)
spl-token create-account AQtCRnZtcD4B9Z4ovd9qEhe1cJo5EvDNB2rtq3tKeemD
运行命令,输出如下:
Creating account ExgvFXbD6W29GijUpwsLsHEiUNeBMxVCbUeTAnqVchoE
Signature: AgBFDkasrAvd1owFGn59oJY2DPT7Gh5Vn5tGhjSmqCiv8AfHBSve9qSQfL8oQwvh6XL7bXJpCHszS6AcPQR7bnw
查看代币账户,运行命令:
spl-token display ExgvFXbD6W29GijUpwsLsHEiUNeBMxVCbUeTAnqVchoE
输出如下:
SPL Token Account
Address: ExgvFXbD6W29GijUpwsLsHEiUNeBMxVCbUeTAnqVchoE
Program: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
Balance: 0
Decimals: 9
Mint: AQtCRnZtcD4B9Z4ovd9qEhe1cJo5EvDNB2rtq3tKeemD
Owner: EZVWcdQu3qVMEhTikTLyQZoBaR9KXWwyxhpiTugRe5Rk
State: Initialized
Delegation: (not set)
Close authority: (not set)
Extensions:
Immutable owner
现在,你打开区块浏览器查看这个新代币,发现 logo 的位置依然显示 Unknown Token,这是为什么呢?
因为我们刚刚没有配置 uri
!在 Solana 的代币元数据标准中,URI 字段通常用于存储指向 JSON 文件的链接。这个 JSON 文件包含代币的详细信息,例如 name、symbol 和 logo URL。
市面上有众多 Web3 存储服务,例如:IPFS
、Web3.Storage
、NFT.Storage
、Arweave
、Pinata
等。它们大都有免费使用的空间,为了避免绑定银行卡,我选择使用 Pinata。
在 <https://pinata.cloud/> 注册成功后,上传文件获得 IPFS CID
,然后就可以通过 https://ipfs.io/ipfs/{IPFS_CID}
公开访问我们的资源了。
Pinata 的使用自行研究即可,非常简单。
选择一个 logo 图标,通过 Pinata 网站上传,获得 IPFS_CID。
我们的代币元数据如下:
{
"name": "My SPL Token",
"symbol": "MST",
"description": "A pig token.",
"image": "https://ipfs.io/ipfs/{IPFS_CID}",
"external_url": "",
"attributes": [],
"properties": {
"files": [
{
"uri": "https://ipfs.io/ipfs/{IPFS_CID}",
"type": "image/png"
}
]
}
}
这里的 image 字段写你的 logo 访问地址,然后按照同样的步骤上传至 Pinata。
获得 ipfs 链接后,我们使用以下命令更新代币的 URI 字段:
spl-token update-metadata AQtCRnZtcD4B9Z4ovd9qEhe1cJo5EvDNB2rtq3tKeemD uri "https://ipfs.io/ipfs/{Your_Pinata_CID}"
更新成功后,再次打开 Solana 区块浏览器,就可以正常看到我们配置的 Logo 和 Name 了。
在 Solana 中,创建代币无需编写智能合约。借助现有的 spl-token
工具,Solana 提供了强大的内置程序和命令行工具,大大简化了代币的创建与管理流程。
通过今天的学习与实践,相信你已经熟悉了创建和发行 Solana 代币的核心步骤。至此,本文内容告一段落。
如果你对 Web3 前沿探索、Go 后端技术,以及产品与哲学的深度思考感兴趣,可以关注我的公众号:认知那些事
。每一篇文章,都是我精心打磨的干货。扫码关注,一起探索技术与思想的广阔世界。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!