1

I'm looking to deploy a new SPL token, hopefully with more custom features. This was some code I scraped together for a standard SPL fungible token. Do you see any issues with it?

I'm doubting myself because I see a lot of other ways to mint SPL tokens, including basic CLI via Solana-CLI, as well as plain Javascript/Typescript.

Why would I need a custom program if there are non-program based ways to mint new SPL tokens?

My understanding is that maybe writing it all in anchor would allow me to add other features besides the plain vanilla SPL correct?

Sorry if this question is dumb - still a beginner and learning along the way.

use anchor_lang::prelude::*;
use anchor_spl::{token::{Token, TokenAccount, Mint}, associated_token::AssociatedToken};
use mpl_token_metadata::types::DataV2;

// Declare the program ID for the spl token
declare_id!("5qoJ4bwuvYbzKDbEhjfZbvsF8A8C6QrMT1LDomq2PMpA");

// The main program module for the spl token
#[program]
pub mod spl_token {
    // Import the system program and various token-related functions from the anchor_spl library
    use anchor_lang::system_program;
    use anchor_spl::{token::{initialize_mint, InitializeMint, mint_to, MintTo, transfer, Transfer, burn, Burn, freeze_account, FreezeAccount, close_account, CloseAccount, thaw_account, ThawAccount, set_authority, SetAuthority}, associated_token, metadata::{create_metadata_accounts_v3, create_master_edition_v3}};
    
    // Import the parent module
    use super::*;

    // Function to create a new spl token
    // Takes in a context of CreateToken, the number of decimals for the token, and the initial amount of the token
    pub fn create_token(ctx: Context<CreateToken>, decimals:u8, amount:u64) -> Result<()> {

        // Create a new account for the mint token
        system_program::create_account(
            CpiContext::new(
                // The system program account
                ctx.accounts.system_program.to_account_info(), 
                // The CreateAccount instruction, specifying the signer and the mint token account
                system_program::CreateAccount { from: ctx.accounts.signer.to_account_info(), to: ctx.accounts.mint_token.to_account_info() }
            ), 
            // The amount of lamports to fund the account with
            10_000_000, 
            // The size of the account data
            82, 
            // The program ID of the account
            ctx.accounts.token_program.key
        )?;

        // Initialize the mint for the token
        initialize_mint(
            CpiContext::new(
                // The token program account
                ctx.accounts.token_program.to_account_info(),
                // The InitializeMint instruction, specifying the mint token account and the rent account
                InitializeMint{mint:ctx.accounts.mint_token.to_account_info(),rent:ctx.accounts.rent.to_account_info()}
            ), 
            // The number of decimals for the token
            decimals, 
            // The mint authority for the token
            ctx.accounts.signer.key, 
            // The freeze authority for the token
            Some(ctx.accounts.signer.key)
        )?;

        // Create an associated token account for the token
        associated_token::create(
            CpiContext::new(
                // The associated token program account
                ctx.accounts.associate_token_program.to_account_info(), 
                // The Create instruction, specifying the payer, the associated token account, the authority, the mint token account, the system program account, and the token program account
                associated_token::Create { 
                    payer: ctx.accounts.signer.to_account_info(), 
                    associated_token: ctx.accounts.token_account.to_account_info(), 
                    authority: ctx.accounts.signer.to_account_info(), 
                    mint: ctx.accounts.mint_token.to_account_info(), 
                    system_program: ctx.accounts.system_program.to_account_info(), 
                    token_program: ctx.accounts.token_program.to_account_info() 
                }
            )
        )?;

        // Mint the initial amount of the token to the associated token account
        mint_to(
            CpiContext::new(
                // The token account
                ctx.accounts.token_account.to_account_info(), 
                // The MintTo instruction, specifying the authority, the mint token account, and the token account
                MintTo{authority:ctx.accounts.signer.to_account_info(),mint:ctx.accounts.mint_token.to_account_info(),to:ctx.accounts.token_account.to_account_info()}
            ), 
            // The amount of the token to mint
            amount
        )?;

        // Return success
        Ok(())
    }

    // Function to transfer spl tokens from one account to another
    // Takes in a context of TransferToken and the amount of the token to transfer
    pub fn transfer_token(ctx: Context<TransferToken>, amount:u64)->Result<()>{

        // Log the start of the transfer
        msg!("Started {:} tokens transfer from account {:} to {:}",amount,ctx.accounts.from_account.key(),ctx.accounts.to_account.key());

        // Transfer the specified amount of the token from the from_account to the to_account
        transfer(
            CpiContext::new(
                // The token program account
                ctx.accounts.token_program.to_account_info(), 
                // The Transfer instruction, specifying the authority, the from_account, and the to_account
                Transfer{authority:ctx.accounts.signer.to_account_info(),from:ctx.accounts.from_account.to_account_info(),to:ctx.accounts.to_account.to_account_info()}
            ), 
            // The amount of the token to transfer
            amount
        )?;

        // Return success
        Ok(())
    }

    // Function to set the authority of a spl token
    // Takes in a context of SetAuthorityToken and the value of the authority to set
    pub fn set_authority_token(ctx: Context<SetAuthorityToken>, authority_value:u8)->Result<()>{
        // Declare variables for the account or mint and the authority type
        let account_or_mint;
        let authority_type;
        // Match the authority_value to set the authority type and the account or mint
        match authority_value {
            // If the authority_value is 0, set the authority type to MintTokens and the account or mint to the mint token account
            0=> {
                authority_type = anchor_spl::token::spl_token::instruction::AuthorityType::MintTokens;
                account_or_mint=ctx.accounts.mint_token.to_account_info();
            },
            // If the authority_value is 1, set the authority type to FreezeAccount and the account or mint to the mint token account
            1=> {
                authority_type = anchor_spl::token::spl_token::instruction::AuthorityType::FreezeAccount;
                account_or_mint=ctx.accounts.mint_token.to_account_info();
            },
            // If the authority_value is 2, set the authority type to AccountOwner and the account or mint to the token account
            2 => {
                authority_type = anchor_spl::token::spl_token::instruction::AuthorityType::AccountOwner;
                account_or_mint = ctx.accounts.token_account.to_account_info();
            },
            // If the authority_value is anything else, set the authority type to CloseAccount and the account or mint to the token account
            _ => {
                authority_type = anchor_spl::token::spl_token::instruction::AuthorityType::CloseAccount;
                account_or_mint = ctx.accounts.token_account.to_account_info();
            }
        }
        // Set the authority of the account or mint
        set_authority(
            CpiContext::new(
                // The token program account
                ctx.accounts.token_program.to_account_info(), 
                // The SetAuthority instruction, specifying the account or mint and the current authority
                SetAuthority{
                    account_or_mint:account_or_mint,
                    current_authority:ctx.accounts.signer.to_account_info()
                }
            ), 
            // The authority type to set
            authority_type.clone(), 
            // The new authority
            Some(ctx.accounts.new_signer.key())
        )?;

        // Return success
        Ok(())
    }

    // Function to burn a specified amount of a spl token
    // Takes in a context of BurnToken and the amount of the token to burn
    pub fn burn_token(ctx: Context<BurnToken>, amount:u64)->Result<()>{
        // Burn the specified amount of the token from the token account
        burn(
            CpiContext::new(
                // The token program account
                ctx.accounts.token_program.to_account_info(), 
                // The Burn instruction, specifying the authority, the from_account, and the mint token account
                Burn{
                    authority:ctx.accounts.signer.to_account_info(),
                    from:ctx.accounts.token_account.to_account_info(),
                    mint:ctx.accounts.mint_token.to_account_info()
                }
            ), 
            // The amount of the token to burn
            amount
        )?;
        // Return success
        Ok(())
    }

    // Function to freeze a spl token
    // Takes in a context of FreezeToken
    pub fn freeze_token(ctx: Context<FreezeToken>)->Result<()>{
        
        // Freeze the token account
        freeze_account(
            CpiContext::new(
                // The token program account
                ctx.accounts.token_program.to_account_info(), 
                // The FreezeAccount instruction, specifying the account, the mint token account, and the authority
                FreezeAccount{
                    account:ctx.accounts.token_account.to_account_info(),
                    mint:ctx.accounts.mint_token.to_account_info(),
                    authority:ctx.accounts.signer.to_account_info(),
                }
            )
        )?;


        // Return success
        Ok(())
    }

    // Function to unfreeze a spl token
    // Takes in a context of FreezeToken
    pub fn un_freeze_token(ctx: Context<FreezeToken>)->Result<()>{
        
        // Unfreeze the token account
        thaw_account(
            CpiContext::new(
                // The token program account
                ctx.accounts.token_program.to_account_info(), 
                // The ThawAccount instruction, specifying the account, the mint token account, and the authority
                ThawAccount{
                    account:ctx.accounts.token_account.to_account_info(),
                    mint:ctx.accounts.mint_token.to_account_info(),
                    authority:ctx.accounts.signer.to_account_info(),
                }
            )
        )?;


        // Return success
        Ok(())
    }

    // Function to close a spl token
    // Takes in a context of CloseToken
    pub fn close_token(ctx: Context<CloseToken>)->Result<()>{
        
        // Close the token account
        close_account(
            CpiContext::new(
                // The token program account
                ctx.accounts.token_program.to_account_info(), 
                // The CloseAccount instruction, specifying the account, the destination, and the authority
                CloseAccount{
                    account:ctx.accounts.token_account.to_account_info(),
                    destination:ctx.accounts.signer.to_account_info(),
                    authority:ctx.accounts.signer.to_account_info(),
                }
            )
        )?;


        // Return success
        Ok(())
    }

    // Function to set the metadata of a spl token
    // Takes in a context of CreateMetadata and the metadata data
    pub fn set_token_metadata(ctx: Context<CreateMetadata>, data:MetadataData)->Result<()>{
        // Find the program address for the metadata
        let (metadata_address, _b1) = Pubkey::find_program_address(&[
            b"metadata", 
            &ctx.accounts.metadata_program.key.to_bytes(),
            &ctx.accounts.mint_token.key().to_bytes()
            ], 
            ctx.accounts.metadata_program.key
        );

        // The metadata account
        let metadata_account = &ctx.accounts.metadata_account;
        // The master account
        let master_account = &ctx.accounts.master_account;

        // Check if the metadata address matches the metadata account key
        if metadata_address != *metadata_account.key{
            // If not, return an error
            return err!(ProgramErrors::PdaNotMatched)
        }
        
        // Find the program address for the master edition
        let (master_address, _b2) = Pubkey::find_program_address(&[
            b"metadata", 
            &ctx.accounts.metadata_program.key.to_bytes(),
            &ctx.accounts.mint_token.key().to_bytes(),
            b"edition"
            ], 
            ctx.accounts.metadata_program.key
        );

        // Check if the master address matches the master account key
        if master_address != *master_account.key{
            // If not, return an error
            return err!(ProgramErrors::PdaNotMatched)
        }

        // Create the metadata accounts for the token
        create_metadata_accounts_v3(
            CpiContext::new(
                // The metadata program account
                ctx.accounts.metadata_program.to_account_info(), 
                // The CreateMetadataAccountsV3 instruction, specifying the metadata account, the mint token account, the mint authority, the update authority, the payer, the system program account, and the rent account
                anchor_spl::metadata::CreateMetadataAccountsV3{
                    metadata:metadata_account.to_account_info(),
                    mint:ctx.accounts.mint_token.to_account_info(),
                    mint_authority:ctx.accounts.signer.to_account_info(),
                    update_authority:ctx.accounts.signer.to_account_info(),
                    payer:ctx.accounts.signer.to_account_info(),
                    system_program:ctx.accounts.system_program.to_account_info(),
                    rent:ctx.accounts.rent.to_account_info(),
                }
            ), 
            // The metadata data
            DataV2 { 
                name: data.name, 
                symbol: data.symbol, 
                uri: data.uri, 
                seller_fee_basis_points: data.seller_fee_basis_points,
                creators: None, 
                collection: None, 
                uses: None 
            },
            // Whether to update the metadata
            true, 
            // Whether to update the authority
            true, 
            // The new authority
            None
        )?;


        // Create the master edition for the token
        create_master_edition_v3(
            CpiContext::new(
                // The metadata program account
                ctx.accounts.metadata_program.to_account_info(), 
                // The CreateMasterEditionV3 instruction, specifying the metadata account, the mint token account, the mint authority, the update authority, the payer, the system program account, the rent account, the edition account, and the token program account
                anchor_spl::metadata::CreateMasterEditionV3 {
                    metadata:metadata_account.to_account_info(),
                    mint:ctx.accounts.mint_token.to_account_info(),
                    mint_authority:ctx.accounts.signer.to_account_info(),
                    update_authority:ctx.accounts.signer.to_account_info(),
                    payer:ctx.accounts.signer.to_account_info(),
                    system_program:ctx.accounts.system_program.to_account_info(),
                    rent:ctx.accounts.rent.to_account_info(),
                    edition:ctx.accounts.edition_account.to_account_info(),
                    token_program:ctx.accounts.token_program.to_account_info()
                }
            ), 
            // The supply of the token
            Some(data.supply)
        )?;

        // Return success
        Ok(())
    }
}

// Struct for the metadata data
#[derive(Debug,AnchorDeserialize,AnchorSerialize)]
pub struct MetadataData{
    // The name of the token
    pub name: String,
    // The symbol of the token
    pub symbol: String,
    // The URI of the token
    pub uri: String,
    // The seller fee basis points of the token
    pub seller_fee_basis_points: u16,
    // The supply of the token
    pub supply: u64
}

// Accounts for the CreateMetadata instruction
#[derive(Accounts)]
pub struct CreateMetadata<'info> {
    // The mint token account
    #[account(mut)]
    pub mint_token:Account<'info,Mint>,
    // The signer account
    #[account(mut)]
    pub signer:Signer<'info>,
    // The metadata account
    #[account(mut)]
    /// CHECK: check for safety in future
    pub metadata_account:AccountInfo<'info>,
    // The master account
    #[account(mut)]
    /// CHECK: check for safety in future
    pub master_account:AccountInfo<'info>,
    // The edition account
    #[account(mut)]
    /// CHECK: check for safety in future
    pub edition_account:AccountInfo<'info>,
    // The system program account
    pub system_program:Program<'info,System>,
    // The token program account
    pub token_program:Program<'info,Token>,
    // The associated token program account
    pub associate_token_program:Program<'info,AssociatedToken>,
    // The metadata program account
    #[account(mut)]
    /// CHECK: check for safety in future
    pub metadata_program:AccountInfo<'info>,
    // The rent account
    pub rent:Sysvar<'info,Rent>
}

// Accounts for the CreateToken instruction
#[derive(Accounts)]
pub struct CreateToken<'info> {
    // The mint token account
    #[account(mut)]
    pub mint_token:Signer<'info>,
    // The signer account
    #[account(mut)]
    pub signer:Signer<'info>,
    // The token account
    #[account(mut)]
    /// CHECK: check for safety in future
    pub token_account:AccountInfo<'info>,
    // The system program account
    pub system_program:Program<'info,System>,
    // The token program account
    pub token_program:Program<'info,Token>,
    // The associated token program account
    pub associate_token_program:Program<'info,AssociatedToken>,
    // The rent account
    pub rent:Sysvar<'info,Rent>
}

// Accounts for the TransferToken instruction
#[derive(Accounts)]
pub struct TransferToken<'info>{    
    // The mint token account
    #[account(mut)]
    pub mint_token:Account<'info,Mint>,
    #[account(mut)]
    pub from_account:Account<'info,TokenAccount>,
    #[account(mut)]
    pub to_account:Account<'info,TokenAccount>,
    #[account(mut)]
    pub signer:Signer<'info>,
    pub system_program:Program<'info,System>,
    pub token_program:Program<'info,Token>,
    pub associate_token_program:Program<'info,AssociatedToken>,
}

#[derive(Accounts)]
pub struct SetAuthorityToken<'info> {
    #[account(mut)]
    pub mint_token:Account<'info,Mint>,
    #[account(mut)]
    pub signer:Signer<'info>,
    #[account(mut)]
    pub new_signer:Signer<'info>,
    #[account(mut)]
    pub token_account:Account<'info,TokenAccount>,
    pub token_program:Program<'info,Token>,
}

#[derive(Accounts)]
pub struct BurnToken<'info> {
    #[account(mut)]
    pub mint_token:Account<'info,Mint>,
    #[account(mut)]
    pub signer:Signer<'info>,
    #[account(mut)]
    pub token_account:Account<'info,TokenAccount>,
    pub token_program:Program<'info,Token>,
}

#[derive(Accounts)]
pub struct FreezeToken<'info> {
    #[account(mut)]
    pub mint_token:Account<'info,Mint>,
    #[account(mut)]
    pub signer:Signer<'info>,
    #[account(mut)]
    pub token_account:Account<'info,TokenAccount>,
    pub token_program:Program<'info,Token>,
}

#[derive(Accounts)]
pub struct CloseToken<'info> {
    #[account(mut)]
    pub mint_token:Account<'info,Mint>,
    #[account(mut)]
    pub signer:Signer<'info>,
    #[account(mut)]
    pub token_account:Account<'info,TokenAccount>,
    pub token_program:Program<'info,Token>,
}

#[error_code]
pub enum ProgramErrors {
    #[msg("PDA account not matched")]
    PdaNotMatched
}

1 Answer 1

1

Your program appears to be a wrapper program around SPL token and metaplex token-metadata, since it calls into the other programs through CPI. This is not a separate token standard.

The token_program that you're passing in is the SPL token program at https://explorer.solana.com/address/TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA, so you're interacting with other programs in yours. It is not possible to mint tokens without a program.

If you want to make your own token standard, you can fork, modify, and deploy your own SPL token program! The source code exists at https://github.com/solana-labs/solana-program-library/tree/master/token/program.

Note that wallets and dapps need to be upgraded in order to support more than the SPL token program, so it will be very difficult to get traction in the ecosystsem, but you can certainly write the code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.