簡介 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, // 代幣賬戶的結構體
},
};
補充
- Sol遊樂場
- Sol瀏覽器