0

I'm new to solana development, with previous EVM experience, so it's hard to change my paradigm😅

I'm wondering what is the best way to lock SPL token transfers temporarily.

So, I have mint initialized inside the program using PDA. I want to lock token transfers for all users for some time and I don't want to fork SPL. One idea that comes into my mind is the token 2022 transfer hook. But I'm not sure if it's a proper implementation. (I'm using anchor)

Can anybody suggest how to do this?

Thank you!

Here part of code I have to init & mint tokens:

use anchor_lang::prelude::*;
use anchor_spl::{ 
    associated_token::AssociatedToken,
    token_interface::{ Mint, TokenAccount },
    token_2022::{ self, MintTo, Token2022, Burn, CloseAccount, SetAuthority, }
};
use spl_token_2022::instruction::AuthorityType;

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

    pub fn initialize(
        ctx: Context<Initialize>,
    ) -> Result<()> {
        let state = &mut ctx.accounts.new_state;

        let (_mint, mint_bump) =
            Pubkey::find_program_address(&[ASSET_TOKEN_SEED, state.key().as_ref()], ctx.program_id);
        state.mint_bump = mint_bump;


        Ok(())
    }

    pub fn purchase(ctx: Context<Purchase>, amount: u64) -> Result<()> {
        let state = & ctx.accounts.state;
        
        let seeds = &[
            &ASSET_TOKEN_SEED[..],
            &state.key().as_ref().to_owned(),
            &[ctx.accounts.state.mint_bump],
        ];
        let signer = [&seeds[..]];
        
        token_2022::mint_to(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                MintTo {
                    authority: ctx.accounts.mint.to_account_info(),
                    to: ctx.accounts.investor_ata.to_account_info(),
                    mint: ctx.accounts.mint.to_account_info(),
                },
                &signer,
            ),
            amount,
        )?;

        token_2022::set_authority(
            CpiContext::new(
                ctx.accounts.token_program.to_account_info(),
                SetAuthority {
                    current_authority: ctx.accounts.investor.to_account_info(),
                    account_or_mint: ctx.accounts.investor_ata.to_account_info(),
                },
            ),
            AuthorityType::CloseAccount,
            Some(*ctx.accounts.mint.to_account_info().key),
        )?;

        Ok(())
    }
}

#[account]
pub struct State {
    pub mint_bump: u8,
}
impl State {
    pub fn space() -> usize {
        8 + 1
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,

    #[account(
        init,
        payer = signer,
        space =  State::space(),
        seeds = [STATE_SEED.as_ref(), signer.key().as_ref()],
        bump
    )]
    pub new_state: Account<'info, State>,

    #[account(
        init,
        seeds = [ASSET_TOKEN_SEED, new_state.key().as_ref()],
        bump,
        payer = signer,
        mint::decimals = 6,
        mint::authority = mint,
        mint::freeze_authority = mint
    )]
    pub mint: InterfaceAccount<'info, Mint>,

    pub system_program: Program<'info, System>,
    pub token_program: Program<'info, Token2022>,
    pub rent: Sysvar<'info, Rent>,
}

#[derive(Accounts)]
pub struct Purchase<'info> {
    #[account()]
    pub state: Account<'info, State>,

    #[account(mut)]
    pub investor: Signer<'info>,

    #[account(
        mut,
        seeds = [ASSET_TOKEN_SEED, state.key().as_ref()],
        bump,
        mint::authority = mint,
    )]
    pub mint: InterfaceAccount<'info, Mint>,
    #[account(
        init_if_needed,
        payer = investor,
        associated_token::mint = mint,
        associated_token::authority = investor,
    )]
    pub investor_ata: InterfaceAccount<'info, TokenAccount>,

    pub rent: Sysvar<'info, Rent>,
    pub token_program: Program<'info, Token2022>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub system_program: Program<'info, System>,
}

1 Answer 1

2

In my opinion you could achieve your goal without using the Token2022 Program but just by using the Normal SPL Token with the following functions:

Then once you want to finally unlock the token you need to use the following functions:

7
  • Thank you! Yeah, this makes sense to me, but this means that I'll have to "un-freeze" ATAs for each user in a loop or using multiple instructions/transactions. That's why the transfer hook looks more elegant solution, I'll have to just switch the flag in state. So I have _beforeTokenTransfer hook from ERC-20 in my mind, but it might be not a great approach on solana. What do you think?
    – Oleh
    Jan 11 at 9:53
  • Yeah, sorry you're completely right that Token2022 might actually be better. What exactly do you need to do? Freeze at the start and then unfreeze or do you want the possibility to freeze and unfreeze as you like?
    – LEO
    Jan 11 at 10:07
  • So, when the user first gets the token, it will be frozen until all tokens are "sold-out". When everything is sold, we will unlock the tokens.
    – Oleh
    Jan 11 at 10:11
  • 1
    Perfect, seems like instead of the transfer hook you might want to use the default extension for Token2022 spl.solana.com/token-2022/extensions#default-account-state. Couldn't find a tutorial for Rust but here's a tutorial on TS: solana.com/developers/guides/token-extensions/…. You can think about maybe actually setting the default extension in Rust and then unfreezing it all on TS. Just make sure that the authority is a Keypair and not a PDA
    – LEO
    Jan 11 at 10:15
  • Thank you a lot! So, basically, should I avoid using the transfer hook, until I really need it? Just wondering what are the downsides of using it.
    – Oleh
    Jan 11 at 10:42

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.