I have a method in my program that can only be fired by the admin address. Now I am trying to fire this method using Next.js server actions using the secret key of the admin address. But I am having issues where the transaction is not being sent. I am facing errors like undefined blockhash and feepayer.What am I missing?
"use server";import { TokenVault } from "@/anchor/types/token_vault";import token_vault_idl from "@/anchor/idl/token_vault.json";import * as anchor from "@coral-xyz/anchor";import { Program, BN, AnchorProvider, Idl } from "@coral-xyz/anchor";import * as web3 from "@solana/web3.js";import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";export async function destake(tokenAccount: string) { const payer = web3.Keypair.fromSecretKey( //secret key here ); const wallet = new NodeWallet(payer); const url = web3.clusterApiUrl("devnet"); let connection = new web3.Connection(url, "confirmed"); const provider = new AnchorProvider(connection, wallet, { commitment: "processed", }); //Create Program Instance const programId = new web3.PublicKey(process.env.NEXT_PUBLIC_PROGRAM_ID); const program = new Program( token_vault_idl as Idl, process.env.NEXT_PUBLIC_PROGRAM_ID, provider ); const DUMMY_TOKEN_ADDRESS = new web3.PublicKey("Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr" ); //Create required PDAs const [tokenAccountOwnerPda] = await web3.PublicKey.findProgramAddressSync( [Buffer.from("token_account_owner_pda")], programId ); const [tokenVault] = await web3.PublicKey.findProgramAddressSync( [Buffer.from("token_vault"), DUMMY_TOKEN_ADDRESS.toBuffer()], programId ); const [stakeInfo] = await web3.PublicKey.findProgramAddressSync( [Buffer.from("stake_info"), new web3.PublicKey(tokenAccount).toBuffer()], program.programId ); console.log("Stake Info", stakeInfo.toString()); const destakeInstruction = await program.methods .destake(new BN(5 * 10 ** 6), payer.publicKey) .accounts({ tokenAccountOwnerPda, vaultTokenAccount: tokenVault, stakeInfo: stakeInfo, mintOfTokenBeingSent: DUMMY_TOKEN_ADDRESS, senderTokenAccount: new web3.PublicKey(tokenAccount), }) .signers([payer]) .transaction(); console.log(destakeInstruction); const txHash = await web3.sendAndConfirmTransaction( connection, destakeInstruction, [payer] ); console.log("Transaction Hash", txHash); const { blockhash, lastValidBlockHeight } = await program.provider.connection.getLatestBlockhash(); await program.provider.connection.confirmTransaction({ blockhash, lastValidBlockHeight, signature: txHash, }); console.log( `Solana Explorer: https://explorer.solana.com/tx/${txHash}?cluster=devnet` );}
Anchor program for reference
use anchor_lang::prelude::*;use anchor_spl::token::{transfer, Mint, Token, TokenAccount, Transfer};use solana_program::clock::Clock;use solana_program::{pubkey, pubkey::Pubkey};pub mod error;pub mod states;use crate::{error::*, states::*};declare_id!("3kgj71kWEa5wFsKkNTjjyBX188zKT1X8ohY6KEst5Eg1");pub const ADMIN: Pubkey = pubkey!("E6q6HeKopLXp447tcqScBFeiFDScHakK26TrTxqHbncE");#[program]mod token_vault { use super::*; pub fn initialize(_ctx: Context<Initialize>) -> Result<()> { Ok(()) } pub fn stake(ctx: Context<Stake>, amount: u64, stake_price: f32) -> Result<()> { msg!("Stake amount: {}!", amount); let clock = Clock::get()?; let stake_info = &mut ctx.accounts.stake_info; stake_info.amount = amount; stake_info.staker = ctx.accounts.signer.key(); stake_info.initial_stake_amount = amount; stake_info.stake_price = stake_price; stake_info.staking_time = clock.unix_timestamp; let transfer_instruction = Transfer { from: ctx.accounts.sender_token_account.to_account_info(), to: ctx.accounts.vault_token_account.to_account_info(), authority: ctx.accounts.signer.to_account_info(), }; let cpi_ctx = CpiContext::new( ctx.accounts.token_program.to_account_info(), transfer_instruction, ); transfer(cpi_ctx, amount)?; Ok(()) } pub fn destake(ctx: Context<DeStake>, amount: u64, staker_address: Pubkey) -> Result<()> { msg!("Destake amount: {}!", staker_address); let stake_info = &mut ctx.accounts.stake_info; if amount == 0 { return Err(error!(Errors::AmountZero)); } if amount > stake_info.amount { return Err(error!(Errors::WithdrawAmountGreater)); } if stake_info.amount == 0 { return Err(error!(Errors::StakeAmountZero)); } let updated_amount = stake_info.amount.checked_sub(amount).unwrap(); ctx.accounts.stake_info.amount = updated_amount; let transfer_instruction = Transfer { from: ctx.accounts.vault_token_account.to_account_info(), to: ctx.accounts.sender_token_account.to_account_info(), authority: ctx.accounts.token_account_owner_pda.to_account_info(), }; let bump = ctx.bumps.token_account_owner_pda; let seeds = &[b"token_account_owner_pda".as_ref(), &[bump]]; let signer = &[&seeds[..]]; let cpi_ctx = CpiContext::new_with_signer( ctx.accounts.token_program.to_account_info(), transfer_instruction, signer, ); transfer(cpi_ctx, amount)?; Ok(()) } pub fn delete_stake_acc(_ctx: Context<DeleteStakeAcc>, _staker_address: Pubkey) -> Result<()> { Ok(()) }}#[derive(Accounts)]pub struct Initialize<'info> { // Derived PDAs #[account( init, payer = signer, seeds=[b"token_account_owner_pda".as_ref()], bump, space = 8 )] token_account_owner_pda: Account<'info, TokenOwnerAccount>, #[account( init, payer = signer, seeds=[b"token_vault".as_ref(), mint_of_token_being_sent.key().as_ref()], token::mint=mint_of_token_being_sent, token::authority=token_account_owner_pda, bump )] vault_token_account: Account<'info, TokenAccount>, mint_of_token_being_sent: Account<'info, Mint>, #[account(mut)] signer: Signer<'info>, system_program: Program<'info, System>, token_program: Program<'info, Token>, rent: Sysvar<'info, Rent>,}#[derive(Accounts)]pub struct Stake<'info> { // Derived PDAs #[account( init, payer = signer, seeds=[b"stake_info", sender_token_account.key().as_ref()], bump, space = 8 + StakeInfo::INIT_SPACE )] pub stake_info: Account<'info, StakeInfo>, #[account( mut, seeds=[b"token_account_owner_pda"], bump )] token_account_owner_pda: Account<'info, TokenOwnerAccount>, #[account(mut, seeds=[b"token_vault", mint_of_token_being_sent.key().as_ref()], bump, token::mint=mint_of_token_being_sent, token::authority=token_account_owner_pda, )] vault_token_account: Account<'info, TokenAccount>, #[account(mut)] sender_token_account: Account<'info, TokenAccount>, mint_of_token_being_sent: Account<'info, Mint>, #[account(mut)] signer: Signer<'info>, system_program: Program<'info, System>, token_program: Program<'info, Token>, rent: Sysvar<'info, Rent>,}#[derive(Accounts)]#[instruction(staker_address: Pubkey)]pub struct DeStake<'info> { // Derived PDAs #[account( mut, seeds=[b"stake_info", sender_token_account.key().as_ref()], bump, )] pub stake_info: Account<'info, StakeInfo>, #[account(mut, seeds=[b"token_account_owner_pda"], bump )] token_account_owner_pda: Account<'info, TokenOwnerAccount>, #[account(mut, seeds=[b"token_vault", mint_of_token_being_sent.key().as_ref()], bump, token::mint=mint_of_token_being_sent, token::authority=token_account_owner_pda, )] vault_token_account: Account<'info, TokenAccount>, #[account(mut)] sender_token_account: Account<'info, TokenAccount>, mint_of_token_being_sent: Account<'info, Mint>, #[account(mut, address = ADMIN @ Errors::NotAdmin)] signer: Signer<'info>, system_program: Program<'info, System>, token_program: Program<'info, Token>, rent: Sysvar<'info, Rent>,}#[derive(Accounts)]#[instruction(staker_address: Pubkey)]pub struct DeleteStakeAcc<'info> { #[account( mut, seeds=[b"stake_info", staker_address.as_ref()], bump, close = signer )] pub stake_info: Account<'info, StakeInfo>, #[account(mut, address = ADMIN @ Errors::NotAdmin)] signer: Signer<'info>,}