Solana playground: Deployed token has no image and metadata

I’m trying to deploy the following token

  • lib.rs
use anchor_lang::prelude::*;
use anchor_spl::{
    associated_token::AssociatedToken,
    metadata::{
        create_metadata_accounts_v3, mpl_token_metadata::types::DataV2, CreateMetadataAccountsV3,
        Metadata as Metaplex,
    },
    token::{self, burn, mint_to, Burn, Mint, MintTo, Token, TokenAccount, Transfer},
};

declare_id!("9nNGmZnBaNk7Fu5BmZtLL35tjmJdrBXtUbRBFhbgHYqE");

pub const PREFIX: &str = "metadata";

fn find_metadata_account(mint: &Pubkey) -> (Pubkey, u8) {
    Pubkey::find_program_address(
        &[
            PREFIX.as_bytes(),
            mpl_token_metadata::ID.as_ref(),
            mint.as_ref(),
        ],
        &mpl_token_metadata::ID,
    )
}


#[program]
mod spl_token {
    use super::*;

    pub fn initialize_token(
        ctx: Context<InitializeToken>,
        params: InitTokenParams,
    ) -> Result<()> {
        // Initialize burn state with our tokenomics parameters
        let burn_state = &mut ctx.accounts.burn_state;
        burn_state.total_supply = 99_999_999_999_999;
        burn_state.burned_amount = 0;
        burn_state.burn_limit = (burn_state.total_supply as f64 * 0.65) as u64;
        burn_state.mint = ctx.accounts.mint.key();
        burn_state.minted_amount = burn_state.total_supply;

        // Mint the total supply to the deployer's token account
        mint_to(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                MintTo {
                    mint: ctx.accounts.mint.to_account_info(),
                    to: ctx.accounts.token_account.to_account_info(),
                    authority: ctx.accounts.mint.to_account_info(),
                },
                &[&[b"mint", &[ctx.bumps.mint]]],
            ),
            burn_state.total_supply,
        )?;

        msg!("BonkBonk initialized successfully with total supply minted to deployer");
        Ok(())
    }

    pub fn transfer(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
        let burn_state = &mut ctx.accounts.burn_state;

        // Calculate burn amount (2% of transfer)
        let burn_amount = (amount as f64 * 0.02) as u64;
        let transfer_amount = amount
            .checked_sub(burn_amount)
            .ok_or(ErrorCode::AmountTooSmall)?;

        // Only burn if we haven't reached the burn limit
        if burn_state.burned_amount < burn_state.burn_limit {
            burn(
                CpiContext::new(
                    ctx.accounts.token_program.to_account_info(),
                    Burn {
                        mint: ctx.accounts.mint.to_account_info(),
                        from: ctx.accounts.from.to_account_info(),
                        authority: ctx.accounts.authority.to_account_info(),
                    },
                ),
                burn_amount,
            )?;

            burn_state.burned_amount = burn_state
                .burned_amount
                .checked_add(burn_amount)
                .ok_or(ErrorCode::NumericalOverflow)?;

            msg!(
                "Burned {} tokens. Total burned: {}",
                burn_amount,
                burn_state.burned_amount
            );
        }

        // Transfer the remaining tokens
        token::transfer(
            CpiContext::new(
                ctx.accounts.token_program.to_account_info(),
                Transfer {
                    from: ctx.accounts.from.to_account_info(),
                    to: ctx.accounts.to.to_account_info(),
                    authority: ctx.accounts.authority.to_account_info(),
                },
            ),
            transfer_amount,
        )?;

        Ok(())
    }
}

#[derive(Accounts)]
#[instruction(params: InitTokenParams)]
pub struct InitializeToken<'info> {
    #[account(
        init,
        seeds = [b"burn_state", mint.key().as_ref()],
        bump,
        payer = payer,
        space = 8 + BurnState::LEN
    )]
    pub burn_state: Account<'info, BurnState>,

    #[account(
        init,
        seeds = [b"mint"],
        bump,
        payer = payer,
        mint::decimals = params.decimals,
        mint::authority = mint,
    )]
    pub mint: Account<'info, Mint>,

    /// CHECK: Metadata account for the mint
    #[account(
        mut,
        address=find_metadata_account(&mint.key()).0
    )]
    pub metadata: UncheckedAccount<'info>,

    #[account(
        init,
        payer = payer,
        associated_token::mint = mint,
        associated_token::authority = payer,
    )]
    pub token_account: 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>,
    /// CHECK: Token metadata program
    #[account(address = mpl_token_metadata::ID)]
    pub token_metadata_program: UncheckedAccount<'info>,
}

#[derive(Accounts)]
pub struct TransferTokens<'info> {
    #[account(
        mut,
        seeds = [b"burn_state", mint.key().as_ref()],
        bump,
    )]
    pub burn_state: Account<'info, BurnState>,

    #[account(mut)]
    pub mint: Account<'info, Mint>,

    #[account(mut)]
    pub from: Account<'info, TokenAccount>,

    #[account(mut)]
    pub to: Account<'info, TokenAccount>,

    pub authority: Signer<'info>,
    pub token_program: Program<'info, Token>,
}

#[derive(Accounts)]
pub struct MintTokens<'info> {
    #[account(
        mut,
        seeds = [b"burn_state", mint.key().as_ref()],
        bump
    )]
    pub burn_state: Account<'info, BurnState>,

    #[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>,
}

#[account]
pub struct BurnState {
    pub total_supply: u64,
    pub burned_amount: u64,
    pub burn_limit: u64,
    pub mint: Pubkey,
    pub minted_amount: u64,
}

impl BurnState {
    pub const LEN: usize = 8 + // discriminator
        8 + // total_supply
        8 + // burned_amount
        8 + // burn_limit
        32 + // mint
        8; // minted_amount
}

#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone)]
pub struct InitTokenParams {
    pub name: String,
    pub symbol: String,
    pub uri: String,
    pub decimals: u8,
}

#[error_code]
pub enum ErrorCode {
    #[msg("Amount too small for burn calculation")]
    AmountTooSmall,
    #[msg("Numerical overflow")]
    NumericalOverflow,
    #[msg("Exceeds maximum supply")]
    ExceedsSupply,
}

And here is my deployment script

  • deploy.ts
import {
  getAccount,
  getAssociatedTokenAddress,
  TOKEN_PROGRAM_ID,
  ASSOCIATED_TOKEN_PROGRAM_ID
} from "@solana/spl-token";
import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY } from "@solana/web3.js";

// Metadata program ID
const TOKEN_METADATA_PROGRAM_ID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s');

// Get the metadata address
const getMetadata = async (mint) => {
  return (
    PublicKey.findProgramAddressSync(
      [
        Buffer.from("metadata"),
        TOKEN_METADATA_PROGRAM_ID.toBuffer(),
        mint.toBuffer(),
      ],
      TOKEN_METADATA_PROGRAM_ID
    )
  )[0];
};

// Initialize
const initialize = async () => {
  // Find PDAs
  let [mintPDA] = PublicKey.findProgramAddressSync(
    [Buffer.from("mint")],
    pg.PROGRAM_ID
  );

  let [burnState] = PublicKey.findProgramAddressSync(
    [Buffer.from("burn_state"), mintPDA.toBuffer()],
    pg.PROGRAM_ID
  );

  const metadata = await getMetadata(mintPDA);

  console.log("Program ID:", pg.PROGRAM_ID.toString());
  console.log("Mint PDA:", mintPDA.toString());
  console.log("Burn State:", burnState.toString());
  console.log("Metadata:", metadata.toString());

  // Token parameters
  const tokenParams = {
    name: "BONKBONK",
    symbol: "BBONK",
    uri: "https://bafybeifgfouc2klsxae66yii5gaee5lk3kqviwqag4nej5fcn4gyuzkx2q.ipfs.w3s.link/",
    decimals: 9,
  };

  try {
    // Get associated token account address
    const associatedTokenAddress = await getAssociatedTokenAddress(
      mintPDA,
      pg.wallet.publicKey
    );
    
    console.log("Associated Token Address:", associatedTokenAddress.toString());

    // Initialize the token with metadata
    let txHash = await pg.program.methods
      .initializeToken(tokenParams)
      .accounts({
        burnState: burnState,
        mint: mintPDA,
        metadata: metadata,
        tokenAccount: associatedTokenAddress,
        payer: pg.wallet.publicKey,
        rent: SYSVAR_RENT_PUBKEY,
        systemProgram: SystemProgram.programId,
        tokenProgram: TOKEN_PROGRAM_ID,
        associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
        tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
      })
      .rpc({ skipPreflight: true });

    console.log("Token initialized successfully!");
    await logTransaction(txHash);

    // Fetch token info
    let tokenAccountInfo = await getAccount(pg.connection, associatedTokenAddress);
    console.log(
      "Token Balance:",
      tokenAccountInfo.amount
    );

    // Fetch burn state
    const burnStateAccount = await pg.program.account.burnState.fetch(burnState);
    console.log("nBurn State Info:");
    console.log("Total Supply:", burnStateAccount.totalSupply.toString());
    console.log("Burned Amount:", burnStateAccount.burnedAmount.toString());
    console.log("Burn Limit:", burnStateAccount.burnLimit.toString());
    console.log("Minted Amount:", burnStateAccount.mintedAmount.toString());

  } catch (error) {
    console.error("Deployment failed:", error);
    throw error;
  }
};

const logTransaction = async (txHash) => {
  const { blockhash, lastValidBlockHeight } =
    await pg.connection.getLatestBlockhash();

  await pg.connection.confirmTransaction({
    blockhash,
    lastValidBlockHeight,
    signature: txHash,
  });

  console.log(
    `View transaction: https://explorer.solana.com/tx/${txHash}?cluster=devnet`
  );
};

// Run initialization
initialize();

This is the info on solana explorer. What am I doing wrong ?

Related Articles