Issues with NFT transfer using createTransferInstruction method

I’m working on a system that moves both SOL tokens and NFTs from user wallets to our main wallet. The process involves creating a transaction with these steps:

  1. Token.createAssociatedTokenAccountInstruction - Only runs if needed for the receiving wallet
  2. Token.createTransferInstruction - Moves the NFT from user’s account to our account
  3. SystemProgram.transfer - Handles the SOL movement

Here’s my implementation using spl-token v0.1.8:

let buildTransferInstructions = async function (tokenMint, sender, receiver, network) {
    let conn = new Connection(network, "confirmed")

    const tokenMintKey = new PublicKey(tokenMint);
    const senderKey = new PublicKey(sender);
    const receiverKey = new PublicKey(receiver);
    
    // FIND SENDER'S TOKEN ACCOUNT
    const senderTokenAccount = await Token.getAssociatedTokenAddress(
        ASSOCIATED_TOKEN_PROGRAM_ID,
        TOKEN_PROGRAM_ID,
        tokenMintKey,
        senderKey
    );
    
    // FIND RECEIVER'S TOKEN ACCOUNT
    const receiverTokenAccount = await Token.getAssociatedTokenAddress(
        ASSOCIATED_TOKEN_PROGRAM_ID,
        TOKEN_PROGRAM_ID,
        tokenMintKey,
        receiverKey
    );

    const targetAccount = await conn.getAccountInfo(receiverTokenAccount);

    const txInstructions = [];

    if (targetAccount === null)
        txInstructions.push(
            Token.createAssociatedTokenAccountInstruction(
                ASSOCIATED_TOKEN_PROGRAM_ID,
                TOKEN_PROGRAM_ID,
                tokenMintKey,
                receiverTokenAccount,
                receiverKey,
                senderKey
            )
        )

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

    return txInstructions;
}

Transaction building code:

var tx = new this.solana.Transaction({
   feePayer: new this.solana.PublicKey(sender),
   recentBlockhash: await hashData.blockhash
});

for (let instruction of nftInstructions) {
   tx.add(instruction);
}

tx.add(this.solana.SystemProgram.transfer({
   fromPubkey: new this.solana.PublicKey(this.provider.publicKey),
   toPubkey: new this.solana.PublicKey(targetWallet.address),
   lamports: 10000000 
}));

Most of the time this works fine and users can approve transactions in their wallets like Phantom or Solflare. But some users report getting validation errors when trying to confirm the transaction in their wallet apps.

The strange part is that these same NFTs can be transferred normally through the wallet’s built-in transfer feature and can be listed on marketplaces without problems. Once the NFT gets moved to a different wallet, my code works perfectly with it.

Could this be related to how the associated token accounts are handled? What might be causing this inconsistent behavior?

I’ve seen this token account authority mismatch before. Your getAssociatedTokenAddress call has the parameters backwards.

You’re doing:

const senderTokenAccount = await Token.getAssociatedTokenAddress(
    ASSOCIATED_TOKEN_PROGRAM_ID,
    TOKEN_PROGRAM_ID,
    tokenMintKey,
    senderKey
);

But it should be mint first, then owner. You’re passing ASSOCIATED_TOKEN_PROGRAM_ID where the mint should go. This screws up the token account addresses and causes transfers to fail validation.

Why it works after moving NFTs to different wallets? Those wallets create accounts with proper authority setup, which hides your parameter ordering bug.

Also check your signer array in createTransferInstruction. Empty arrays work for basic cases, but some NFTs need proper authority signatures depending on how they were minted.

Interesting issue! I’ve seen this before and it always makes you wonder what’s happening behind the scenes.

The fact that NFTs work fine after moving to a different wallet once is a big clue. Sounds like there’s something wrong with the initial token account setup or metadata state.

Couple questions - are you checking the token account’s state before trying the transfer? Sometimes the account exists but it’s in a weird state (frozen or has unexpected delegate settings). What validation errors are users actually seeing? “Insufficient funds”, “invalid account”, or something else?

Also wondering if these problematic NFTs were minted using different standards or older token program versions. NFTs created with earlier tooling sometimes have slightly different account structures that cause issues.

You could try adding some debugging to check the actual token account data before building instructions. Log the account owner, delegate, and amount fields to see if anything looks off.

What version of solana web3.js are you running with that spl-token v0.1.8? Have you noticed patterns with specific wallets or NFTs causing problems? Would help to narrow down the common factors.

I’ve hit this before - it’s usually the token account delegate or freeze authority causing issues. some NFTs get minted with delegate settings that block transfers even tho they appear normal. check getTokenAccountsByOwner before you create the transfer. It’ll show delegate probs your code might be missing.