你的第一个Solana SPL
简介 TFT
你的第一个SPL The first token
技术栈和库
- Rust
- Anchor框架
- Typescript(测试)
开发环境和其它网络地址
- DevNet: https://api.devnet.solana.com
- TestNet: https://api.testnet.solana.com
- MainNet: https://api.mainnet-beta.solana
开发环境设置
1.本教程使用的时 DevNet
2.浏览器打开 https://beta.solpg.io/
3.创建项目
4.请求空头

请求空投

Sol程序开发
// ========= Step 1 引用框架
// 1.管理账户的
use anchor_lang::prelude::*;
// 2.管理代币的
use anchor_spl::{
associated_token::AssociatedToken, // 处理关联代币账户的功能
metadata::{
create_metadata_accounts_v3, // 创建元数据账户的功能
mpl_token_metadata::types::DataV2, // 元数据的结构体定义
CreateMetadataAccountsV3, // 创建元数据账户的指令结构体
Metadata as Metaplex, // 将 Metadata 重命名为 Metaplex,以便于使用
},
token::{
mint_to, // 铸币功能
Mint, // 代币铸造的结构体
MintTo, // 铸币指令的结构体
Token, // 代币的基本功能
TokenAccount, // 代币账户的结构体
},
};
// 2.加载程序id(自己获取,或者系统生成)
declare_id!("7CR9ATZRxzEmCSM91UkumMJ6b8h5ompMcxTnUKLc8z4e");
// 3.代币主程序
#[program]
mod token_minter {
use super::*;
// 3.1初始化 SPL
pub fn init_token(ctx: Context<InitToken>, metadata: InitTokenParams) -> Result<()> {
let seeds = &["mint".as_bytes(), &[ctx.bumps.mint]];
let signer = [&seeds[..]];
let token_data: DataV2 = DataV2 {
name: metadata.name,
symbol: metadata.symbol,
uri: metadata.uri,
seller_fee_basis_points: 0,
creators: None,
collection: None,
uses: None,
};
let metadata_ctx = CpiContext::new_with_signer(
ctx.accounts.token_metadata_program.to_account_info(),
CreateMetadataAccountsV3 {
payer: ctx.accounts.payer.to_account_info(),
update_authority: ctx.accounts.mint.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
metadata: ctx.accounts.metadata.to_account_info(),
mint_authority: ctx.accounts.mint.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
rent: ctx.accounts.rent.to_account_info(),
},
&signer,
);
create_metadata_accounts_v3(metadata_ctx, token_data, false, true, None)?;
msg!("Token mint created successfully.");
Ok(())
}
// 3.2 铸造 SPL
pub fn mint_tokens(ctx: Context<MintTokens>, quantity: u64) -> Result<()> {
let seeds = &["mint".as_bytes(), &[ctx.bumps.mint]];
let signer = [&seeds[..]];
mint_to(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
MintTo {
authority: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.destination.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
},
&signer,
),
quantity,
)?;
Ok(())
}
}
// 4.主程序需要的账户
#[derive(Accounts)]
#[instruction(params: InitTokenParams)]
pub struct InitToken<'info> {
// Metaplex 账户
#[account(mut)]
pub metadata: UncheckedAccount<'info>,
#[account(
init,
seeds = [b"mint"],
bump,
payer = payer,
mint::decimals = params.decimals,
mint::authority = mint,
)]
pub mint: Account<'info, Mint>,
#[account(mut)]
pub payer: Signer<'info>,
pub rent: Sysvar<'info, Rent>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
pub token_metadata_program: Program<'info, Metaplex>,
}
#[derive(Accounts)]
pub struct MintTokens<'info> {
#[account(
mut,
seeds = [b"mint"],
bump,
mint::authority = mint,
)]
pub mint: Account<'info, Mint>,
#[account(
init_if_needed,
payer = payer,
associated_token::mint = mint,
associated_token::authority = payer,
)]
pub destination: Account<'info, TokenAccount>,
#[account(mut)]
pub payer: Signer<'info>,
pub rent: Sysvar<'info, Rent>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
}
// 5.账户的数据
// 5. 定义init令牌参数
#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone)]
pub struct InitTokenParams {
pub name: String,
pub symbol: String,
pub uri: String,
pub decimals: u8,
}
部署

部署完成


测试
替换anchor.test.ts内容

describe("Test Minter", () => {
const METADATA_SEED = "metadata";
const TOKEN_METADATA_PROGRAM_ID = new web3.PublicKey(
"F64uG9fPnEZYZ6G4Nbbuz6D715gYAKw1j71etHLNjHx2"
); // 你的程序 ID,和程序相同
const MINT_SEED = "mint";
// SPL基础信息
const payer = pg.wallet.publicKey;
const metadata = {
name: "My The first token",
symbol: "TFT",
uri: "https://5vfxc4tr6xoy23qefqbj4qx2adzkzapneebanhcalf7myvn5gzja.arweave.net/7UtxcnH13Y1uBCwCnkL6APKsge0hAgacQFl-zFW9NlI",
decimals: 9,
};
const mintAmount = 1000;
const [mint] = web3.PublicKey.findProgramAddressSync(
[Buffer.from(MINT_SEED)],
pg.PROGRAM_ID
);
const [metadataAddress] = web3.PublicKey.findProgramAddressSync(
[
Buffer.from(METADATA_SEED),
TOKEN_METADATA_PROGRAM_ID.toBuffer(),
mint.toBuffer(),
],
TOKEN_METADATA_PROGRAM_ID
);
// 测试初始化
it("initialize", async () => {
const info = await pg.connection.getAccountInfo(mint);
if (info) {
return;
}
console.log(" Mint not found. Attempting to initialize.");
const context = {
metadata: metadataAddress,
mint,
payer,
rent: web3.SYSVAR_RENT_PUBKEY,
systemProgram: web3.SystemProgram.programId,
tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID,
tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
};
const tx = await pg.program.methods
.initToken(metadata)
.accounts(context)
.transaction();
const txHash = await web3.sendAndConfirmTransaction(
pg.connection,
tx,
[pg.wallet.keypair],
{ skipPreflight: true }
);
console.log(` https://explorer.solana.com/tx/${txHash}?cluster=devnet`);
const newInfo = await pg.connection.getAccountInfo(mint);
assert(newInfo, " Mint should be initialized.");
});
// 测试铸造
it("mint tokens", async () => {
const destination = await anchor.utils.token.associatedAddress({
mint: mint,
owner: payer,
});
let initialBalance: number;
try {
const balance = await pg.connection.getTokenAccountBalance(destination);
initialBalance = balance.value.uiAmount;
} catch {
// Token account not yet initiated has 0 balance
initialBalance = 0;
}
const context = {
mint,
destination,
payer,
rent: web3.SYSVAR_RENT_PUBKEY,
systemProgram: web3.SystemProgram.programId,
tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID,
associatedTokenProgram: anchor.utils.token.ASSOCIATED_PROGRAM_ID,
};
const txHsh = await pg.program.methods
.mintTokens(new BN(mintAmount * 10 ** metadata.decimals))
.accounts(context)
.signers([pg.wallet.keypair])
.rpc();
// const txHash = await web3.sendAndConfirmTransaction(
// pg.connection,
// tx,
// [pg.wallet.keypair],
// { skipPreflight: true }
// );
console.log(`mint Hash =>`, txHsh);
const postBalance = (
await pg.connection.getTokenAccountBalance(destination)
).value.uiAmount;
assert.equal(
initialBalance + mintAmount,
postBalance,
"Post balance should equal initial plus mint amount"
);
});
});
铸造
运行测试代码,进行SPL铸造, 记得把密钥导入 Phantom(切换网络)

增发
注释初始化代码,增加第二次SPL铸造

总结
Anchor框架总结
// 1.管理账户的
use anchor_lang::prelude::*;
// 管理代币的
use anchor_spl::{
associated_token::AssociatedToken, // 处理关联代币账户的功能
metadata::{
create_metadata_accounts_v3, // 创建元数据账户的功能
mpl_token_metadata::types::DataV2, // 元数据的结构体定义
CreateMetadataAccountsV3, // 创建元数据账户的指令结构体
Metadata as Metaplex, // 将 Metadata 重命名为 Metaplex,以便于使用
},
token::{
mint_to, // 铸币功能
Mint, // 代币铸造的结构体
MintTo, // 铸币指令的结构体
Token, // 代币的基本功能
TokenAccount, // 代币账户的结构体
},
};
补充
你的第一个Solana SPL的更多相关文章
- u-boot SPL的理解
uboot分为uboot-spl和uboot两个组成部分.SPL是Secondary Program Loader的简称,第二阶段程序加载器,这里所谓的第二阶段是相对于SOC中的BROM来说的,之前的 ...
- tiny210——uboot移植Makefile文章分析
这东西已经写,我们没有时间发布,如今,终于有时间稍微长送记录汇总uboot学习过程.具体了.以后忘了也能够再温习回来嘛有些特殊字符显示得乱掉了 Makefile追踪技巧: 技巧1:能够先从编译目标開始 ...
- U-boot的编译方式及目录结构解析
U-boot的整体结构和linux基本类似,编译方式一般也是非常类似的,一般的编译命令: make CROSS_COMPILE=arm-linux-gnueabihf- XXX(目标名) 清除命令: ...
- 为什么很多人坚信“富贵险中求”?
之家哥 2017-11-15 09:12:31 微信QQ微博 下载APP 摘要 网贷之家小编根据舆情频道的相关数据,精心整理的关于<为什么很多人坚信"富贵险中求"?>的 ...
- python基础全部知识点整理,超级全(20万字+)
目录 Python编程语言简介 https://www.cnblogs.com/hany-postq473111315/p/12256134.html Python环境搭建及中文编码 https:// ...
- Java遇上SPL:架构优势和开发效率,一个不放过
摘要:如果我们在Java中也提供有一套完整的结构化数据处理和计算类库,那这个问题就能得到解决:即享受到架构的优势,又不致于降低开发效率. 本文分享自华为云社区<Java结构化处理SPL>, ...
- PHP 高级编程(3/5) - 使用SPL(标准PHP库)实现观察者模式
SPL(标准PHP库 - Standard PHP Library)是PHP5面向对象功能中重要的部分.原文解释是这样的“The Standard PHP Library (SPL) is a col ...
- PHP SPL(PHP 标准库)
一.什么是SPL? SPL是用于解决典型问题(standard problems)的一组接口与类的集合.(出自:http://php.net/manual/zh/intro.spl.php) SPL, ...
- 自己使用的一个.NET轻量开发结构
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIgAAABFCAIAAAAerjlvAAAE2UlEQVR4nO2a3U/bVhiH+bdyPaqpmx
- 【夯实PHP基础】PHP标准库 SPL
PHP SPL笔记 这几天,我在学习PHP语言中的SPL. 这个东西应该属于PHP中的高级内容,看上去很复杂,但是非常有用,所以我做了长篇笔记.不然记不住,以后要用的时候,还是要从头学起. 由于这是供 ...
随机推荐
- 实操教程 | 触发器实现 Apache DolphinScheduler 失败钉钉自动告警
作者 | sqlboy-yuzhenc 背景介绍 在实际应用中,我们经常需要将特定的任务通知给特定的人,虽然 Apache DolphinScheduler 在安全中心提供了告警组和告警实例,但是配置 ...
- Apache SeaTunnel 社区 3 月月报
各位热爱 SeaTunnel 的小伙伴们,SeaTunnel 社区 3 月月报来啦!这里将记录 SeaTunnel 社区每个月的重要更新,并评选出月度之星,欢迎关注. SeaTunnel 月度 Mer ...
- 从0实现基于Linux socket聊天室-实现聊天室的公聊、私聊功能-4
前面文章链接如下: <从0实现基于Linux socket聊天室-多线程服务器模型-1> <从0实现基于Linux socket聊天室-多线程服务器一个很隐晦的错误-2> &l ...
- 关于centos7下所有指令失效
起因: 疑似宝塔更新修复后,本地所有环境变量集体不生效 问题环境 xshell环境下ssh连接 问题描述: - bash: xxx not fund - 环境变量无法保存 - 所有的保存方式都是临时生 ...
- 防止npm被墙的小技巧
方法一: 全局安装中国服务器的包管理工具 npm i cnpm --global 下载包的时候用cnpm取代npm 方法二: 在小黑板输入:npm config set registry https: ...
- elasticsearch单机版安装及安装过程踩的坑整理
elasticsearch单机版安装及安装过程踩的坑整理 环境及版本 Linux版本:centos7.3 JDK版本:1.8 Elasticsearch版本: Linux用户:esuser 说明:因为 ...
- qumu虚拟机启动后无法远程连接
通过 virsh 在启动 qemu 虚拟机,可以通过 VNC 访问虚拟机,但无法通过设置的 SSH 的外部映射端口登录.首先在宿主机上查看虚拟机(csv\tpm\name)的网络配置,可以看到端口映射 ...
- 小tips:npm与npx的区别
npm npm是Node.js的软件包管理器,其目标是自动化的依赖性和软件包管理. 这意味着,可以在package.json文件中为项目指定所有依赖项(软件包),当需要为其安装依赖项时,只要运行npm ...
- Dubbo框架的1个核心设计点
Java领域要说让我最服气的RPC框架当属Dubbo,原因有许多,但是最吸引我的还是它把远程调用这个事情设计得很有艺术. 1.Dubbo优点较多,我只钟情其一 1.1.优点 业内对于微服务之间调用的框 ...
- QT原理与源码分析之QT反射机制原理
QT反射机制原理 本文将介绍QT反射机制创建QT对象实例的原理和流程以及源代码. 文章目录 QT反射机制创建QT对象实例 原理 流程 源码 QT反射机制创建QT对象实例 QT框架提供的基于元对象的反射 ...