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 ?