NFT transfer implementation using Solana web3.js throws InvalidAccountData error

I’m building a staking platform where users can deposit their NFTs to earn tokens. I need to move the NFT from the user’s wallet to my program’s wallet but I keep getting an error.

Here’s my current implementation:

import { Connection, PublicKey, Transaction } from "@solana/web3.js";
import * as anchor from "@project-serum/anchor";
import { web3 } from "@project-serum/anchor";
import {
  Token,
  TOKEN_PROGRAM_ID,
  ASSOCIATED_TOKEN_PROGRAM_ID,
} from "@solana/spl-token";

const transferNFTToVault = async function (tokenMint: string, userWallet: Wallet, vaultAddress: string) {
  let rpcConnection = new Connection("https://api.devnet.solana.com");

  const nftMintKey = new web3.PublicKey(tokenMint);
  const senderKey = userWallet.publicKey;
  const vaultKey = new web3.PublicKey("VAULT_WALLET_PUBLIC_KEY");

  const tokenInstance = new Token(
    rpcConnection,
    nftMintKey,
    TOKEN_PROGRAM_ID,
    userWallet.payer
  );

  // FIND SENDER TOKEN ACCOUNT
  const senderTokenAccount = await Token.getAssociatedTokenAddress(
    tokenInstance.associatedProgramId,
    tokenInstance.programId,
    nftMintKey,
    senderKey
  );

  // FIND VAULT TOKEN ACCOUNT
  const vaultTokenAccount = await Token.getAssociatedTokenAddress(
    tokenInstance.associatedProgramId,
    tokenInstance.programId,
    nftMintKey,
    vaultKey
  );

  const vaultAccountInfo = await rpcConnection.getAccountInfo(
    vaultTokenAccount
  );

  const txInstructions = [];

  if (vaultAccountInfo === null) {
    console.log("vault account doesn't exist, creating it");
    txInstructions.push(
      Token.createAssociatedTokenAccountInstruction(
        tokenInstance.associatedProgramId,
        tokenInstance.programId,
        nftMintKey,
        vaultTokenAccount,
        vaultKey,
        senderKey
      )
    );
  }

  txInstructions.push(
    Token.createTransferInstruction(
      TOKEN_PROGRAM_ID,
      senderTokenAccount,
      vaultTokenAccount,
      senderKey,
      [],
      1
    )
  );

  let finalTransaction = null;
  for (let idx = 0; idx < txInstructions.length; idx++) {
    finalTransaction = new web3.Transaction().add(txInstructions[idx]);
  }

  if (finalTransaction) {
    let txResult = await userWallet.sendTransaction(finalTransaction, rpcConnection);
    console.log("transaction result: ", txResult);
  } else {
    console.log("Failed to build transaction");
  }
};

export default transferNFTToVault;

The wallet popup appears and user confirms the transaction but then I get this error:

Transaction simulation failed: Error processing Instruction 0: invalid account data for instruction 
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [1]
Program log: Instruction: Transfer
Program log: Error: InvalidAccountData

I tried replacing the program IDs with the imported constants but still no luck. What could be causing this InvalidAccountData error?

you’re using the old spl-token library. same thing happened to me last week - the Token class doesn’t work anymore. switch to @solana/spl-token methods instead of Token constructor. also double-check your vault wallet has authority to receive tokens. that’s usually what causes InvalidAccountData errors.

Interesting issue! I’ve been working on something similar and might’ve hit the same problem.

Are you sure the sender token account exists and actually has the NFT? I see you’re checking if the vault account exists but not verifying the sender account. Try adding a quick check for the sender token account info.

Also - when you say “VAULT_WALLET_PUBLIC_KEY” is that a placeholder or are you hardcoding that string? If it’s hardcoded, that’ll definitely cause issues.

For debugging: what’s the sender token account balance before the transfer? Use getTokenAccountBalance to check if the NFT’s actually there. Sometimes the account exists but is empty, which throws weird errors.

Have you tried running this on localnet first? You’ll get better error messages. Devnet can be finicky and the logs aren’t always helpful.

It seems the issue stems from how you’re constructing the transaction. You’re currently overwriting the finalTransaction in each iteration of the loop. Instead, you should instantiate the finalTransaction once and then add each instruction using a loop. Here’s how you can adjust it:

const finalTransaction = new web3.Transaction();
txInstructions.forEach(instruction => {
  finalTransaction.add(instruction);
});

Additionally, consider that the @solana/spl-token package has undergone updates. Instead of using the now-deprecated Token class, opt for the newer methods like getAssociatedTokenAddress and createTransferInstruction. This adjustment resolved similar InvalidAccountData errors I faced during NFT transfers in my own implementation.