你的第一个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, // 代币账户的结构体
    },
};

补充

  • Sol游乐场
  • Sol浏览器
上一篇:git ls-remote-4.示例


下一篇:[ 问题解决篇 ] 解决windows虚拟机安装vmtools报错-winserver2012安装vmtools及安装KB2919355补丁 (附离线工具)-1 前言