How to transfer NFTs between wallets using Solana web3.js library

I’m building a dapp where users can deposit their NFTs into a staking contract to get rewards. I need help creating a transaction that moves the NFT from user wallet to my program wallet.

My current code:

import { Connection, PublicKey } 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 transferNFTToStaking = async function (tokenMint: string, userWallet: Wallet, stakingAddress: string) {
  let rpcConnection = new Connection("https://api.devnet.solana.com");

  const nftMintKey = new web3.PublicKey(tokenMint);
  const userPubkey = userWallet.publicKey;
  const stakingPubkey = new web3.PublicKey("STAKING_PROGRAM_WALLET");

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

  // FIND USER TOKEN ACCOUNT
  const userTokenAccount = await Token.getAssociatedTokenAddress(
    tokenInstance.associatedProgramId,
    tokenInstance.programId,
    nftMintKey,
    userPubkey
  );

  // FIND STAKING TOKEN ACCOUNT
  const stakingTokenAccount = await Token.getAssociatedTokenAddress(
    tokenInstance.associatedProgramId,
    tokenInstance.programId,
    nftMintKey,
    stakingPubkey
  );

  const stakingAccountInfo = await rpcConnection.getAccountInfo(
    stakingTokenAccount
  );

  const txInstructions = [];

  if (stakingAccountInfo === null) {
    console.log("staking account needs creation");
    txInstructions.push(
      Token.createAssociatedTokenAccountInstruction(
        tokenInstance.associatedProgramId,
        tokenInstance.programId,
        nftMintKey,
        stakingTokenAccount,
        stakingPubkey,
        userPubkey
      )
    );
  }

  txInstructions.push(
    Token.createTransferInstruction(
      TOKEN_PROGRAM_ID,
      userTokenAccount,
      stakingTokenAccount,
      userPubkey,
      [],
      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 transferNFTToStaking;

When I test this the wallet popup shows up and user approves it 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
    Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1781 of 200000 compute units
    Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA failed: invalid account data for instruction

I already tried using the imported constants instead of the token instance properties but still getting the same error. What could be wrong with my approach?

I’ve hit this same issue before. Usually comes down to account validation problems. You need to ditch those old Token class methods - they don’t play nice with newer token standards. Switch to the latest spl-token functions like getAssociatedTokenAddressSync instead. Also check how you’re building your transaction. Don’t create a new transaction for each instruction in your loop - batch all instructions into one transaction. Before you transfer anything, verify the user’s token account actually works and has the NFT you expect using getTokenAccountBalance. I’ve seen accounts that look fine but have zero balance or wrong token data, which throws that InvalidAccountData error. And make sure you’re using the right mint address - token types can be tricky.

your loop’s broken - you’re overwriting finalTransaction every time instead of adding instructions. use finalTransaction = new web3.Transaction().add(...txInstructions) or build it right. also, ditch that token class - it’s deprecated. use getAssociatedTokenAddressSync from spl-token. that invalid account data error? usually means the user’s token account doesn’t exist or has bad data.

Interesting issue! I’ve hit this “invalid account data” error before with NFT transfers - it’s usually something sneaky :thinking:

First question: does the user actually own that NFT? Check if userTokenAccount exists and has a balance of 1. The associated token account might not exist on the user’s side either.

Your transaction building logic looks off. You’re creating a new transaction for each instruction in the loop but only keeping the last one? Add all instructions to the same transaction instead of overwriting it.

What wallet are you testing with? Phantom, Solflare? Some wallets get finicky with transaction signing.

Try logging the actual account addresses before building the transaction. Double-check that getAssociatedTokenAddress returns what you expect - mint address formatting can cause weird issues.

Does a simple SOL transfer work with your wallet setup? Start there instead of the NFT transfer.