I'm not the first to ask a question about this problem, but none of the questions asked were answered. The problem is that I'm trying to create an associated token account to mint an NFT, but the spl_associated_token_account::instruction::create_associated_token_account
fails, when invoked. It seems like I've tried everything and read all the related sources, but nothing works for me.
Test Snippet
Here's the snippet of the test that fails:
/* Another test code... */
#[tokio::test]
async fn test_mint_created_nft_token() {
msg!("Running `test_mint_created_nft_token`...");
// Setting up the testing environment
let (program_id, program_test) = initialize_program_test();
msg!("Program ID: {}", program_id);
let ctx = program_test.start_with_context().await;
let mint_authority_id = ctx.payer.pubkey();
let payer_id = ctx.payer.pubkey();
let mint_account = Keypair::new();
let mint_account_id = mint_account.pubkey();
let wallet = Keypair::new();
let wallet_id = wallet.pubkey();
let (edition_account_id, _) = Pubkey::find_program_address(
&[
"metadata".as_bytes(),
mpl_token_metadata::ID.as_ref(),
&mint_account_id.to_bytes(),
"edition".as_bytes(),
],
&mpl_token_metadata::ID,
);
let (metadata_account_id, _) = Pubkey::find_program_address(
&[
"metadata".as_bytes(),
mpl_token_metadata::ID.as_ref(),
&mint_account_id.to_bytes(),
],
&mpl_token_metadata::ID,
);
let associated_token_account_id =
get_associated_token_address_with_program_id(
&payer_id,
&mint_account_id,
&spl_token::ID,
);
// Create NFT
msg!("Creating NFT...");
// Initializing instruction to create NFT
let ix = Instruction::new_with_borsh(
program_id,
&SplNftMinterIntstruction::Create(CreateNftTokenArgs {
name: "Ballet Dancers BTC".to_string(),
symbol: "BDB".to_string(),
uri: "https://storage.yandexcloud.net/lab-bucket/item.json"
.to_string(),
}),
vec![
AccountMeta::new(metadata_account_id, false),
AccountMeta::new(mint_account_id, true),
AccountMeta::new(mint_authority_id, true),
AccountMeta::new(payer_id, true),
AccountMeta::new_readonly(rent::ID, false),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(mpl_token_metadata::ID, false),
AccountMeta::new_readonly(spl_token::ID, false),
],
);
// Initializing transaction with the instruction above
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&payer_id),
&[&ctx.payer, &mint_account],
ctx.last_blockhash,
);
// Executing the transaction above
let tx_result = ctx.banks_client.process_transaction(tx).await;
assert!(tx_result.is_ok());
msg!("NFT created successfully!");
// Mint the created NFT
msg!("Minting NFT...");
// Initializing instruction to mint the created NFT
let ix = Instruction::new_with_borsh(
program_id,
&SplNftMinterIntstruction::Mint,
vec![
AccountMeta::new(associated_token_account_id, false),
AccountMeta::new_readonly(
spl_associated_token_account::ID,
false,
),
AccountMeta::new(edition_account_id, false),
AccountMeta::new(metadata_account_id, false),
AccountMeta::new(mint_account_id, true),
AccountMeta::new(mint_authority_id, false),
AccountMeta::new(payer_id, true),
AccountMeta::new_readonly(rent::ID, false),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(spl_token::ID, false),
AccountMeta::new_readonly(mpl_token_metadata::ID, false),
AccountMeta::new_readonly(wallet_id, false),
],
);
// Initializing transaction with the instruction above
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&payer_id),
&[&ctx.payer, &mint_account],
ctx.last_blockhash,
);
// Executing the transaction above
let tx_result = ctx.banks_client.process_transaction(tx).await;
assert!(tx_result.is_ok());
msg!("NFT minted successfully!");
}
/* Another test code... */
Instruction Snippet
It basically tries to call the following instruction:
pub fn mint_to(accounts: &[AccountInfo]) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
let associated_token_account = next_account_info(accounts_iter)?;
let _associated_token_program = next_account_info(accounts_iter)?;
let edition_account = next_account_info(accounts_iter)?;
let metadata_account = next_account_info(accounts_iter)?;
let mint_account = next_account_info(accounts_iter)?;
let mint_authority = next_account_info(accounts_iter)?;
let payer = next_account_info(accounts_iter)?;
let rent = next_account_info(accounts_iter)?;
let system_program = next_account_info(accounts_iter)?;
let token_metadata_program = next_account_info(accounts_iter)?;
let token_program = next_account_info(accounts_iter)?;
let wallet = next_account_info(accounts_iter)?;
if associated_token_account.lamports() != 0 {
msg!("Associated token account exists.");
} else {
msg!("Creating associated token account...");
// TODO: Resolve the issue with `create_associated_token_account()`
// invocation
invoke(
&ata_instruction::create_associated_token_account(
payer.key,
wallet.key,
mint_account.key,
token_program.key,
),
&[
payer.clone(),
associated_token_account.clone(),
wallet.clone(),
mint_account.clone(),
system_program.clone(),
token_program.clone(),
],
)?;
}
msg!("Associated Token Address: {}", associated_token_account.key);
// Mint the NFT to the user's wallet
msg!("Minting NFT to associated token account...");
invoke(
&token_instruction::mint_to(
token_program.key,
mint_account.key,
associated_token_account.key,
mint_authority.key,
&[mint_authority.key],
1,
)?,
&[
mint_account.clone(),
mint_authority.clone(),
associated_token_account.clone(),
token_program.clone(),
],
)?;
// We can make this a Limited Edition NFT through Metaplex,
// which will disable minting by setting the Mint & Freeze Authorities to
// the Edition Account
msg!("Creating edition account...");
msg!("Edition account address: {}", edition_account.key);
invoke(
&CreateMasterEditionV3Builder::new()
.edition(*edition_account.key)
.mint(*mint_account.key)
.update_authority(*mint_authority.key)
.mint_authority(*mint_authority.key)
.payer(*payer.key)
.metadata(*metadata_account.key)
.token_program(*token_program.key)
.system_program(*system_program.key)
.rent(Some(*rent.key))
.max_supply(1)
.instruction(),
&[
edition_account.clone(),
metadata_account.clone(),
mint_account.clone(),
mint_authority.clone(),
payer.clone(),
token_metadata_program.clone(),
rent.clone(),
],
)?;
msg!("NFT minted successfully!");
Ok(())
}
Test Logs
There're testing logs:
...
[2024-12-01T23:25:37.444637117Z DEBUG solana_runtime::message_processor::stable_log] Program log: Creating associated token account...
[2024-12-01T23:25:37.444838705Z DEBUG solana_runtime::message_processor::stable_log] Instruction references an unknown account GiPqmxsWPwCYUW2yu68ZZovJwxk9ZVGsK997mwvMgdxr
[2024-12-01T23:25:37.448646550Z DEBUG solana_runtime::message_processor::stable_log] Program 1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM consumed 7564 of 200000 compute units
[2024-12-01T23:25:37.448690919Z DEBUG solana_runtime::message_processor::stable_log] Program 1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM failed: An account required by the instruction is missing
test processor::test::test_mint_created_nft_token ... FAILED
failures:
---- processor::test::test_mint_created_nft_token stdout ----
Running `test_mint_created_nft_token`...
Program ID: 1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM
Creating NFT...
NFT created successfully!
Minting NFT...
thread 'processor::test::test_mint_created_nft_token' panicked at src/processor.rs:236:9:
assertion failed: tx_result.is_ok()
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
processor::test::test_mint_created_nft_token
test result: FAILED. 1 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.42s
error: test failed, to rerun pass `--lib`
More Details
If you want a fully reproducible code, you can clone it from GitHub. The base was originally taken from official Solana program examples.