你的第一個Solana SPL

半截肥皂發表於2024-10-31

簡介 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瀏覽器

相關文章