1

I'm trying to use the lever/hand example together with the SPL token example, but I keep getting:

Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x2

and I can't seem to fix it. Basically what I'm trying to do is:

  • Create token and mint some tokens to AccountX: this works
  • Transfer tokens from AccountX to a PDA owned by the sendTokens contract: this also works
  • Transfer tokens from the PDA contract to AccountY by calling the "transferTokensToUser" function in the sendTokens contract: this fails.

This is the contract I have (with some address and name edits for privacy):

import "../libraries/system_instruction.sol";
import "solana";

// Interface to the token program.
tokenInterface constant tokenProgram = tokenInterface(address'7...');
interface tokenInterface {
    function transferTokens(address from, address to, uint64 amount) external;
}

@program_id("B...")
contract sendTokens {

    @payer(payer)
    @seed("newseed_pda")
    constructor(@bump bytes1 bump, uint64 fundLamports) {
        // Independently derive the PDA address from the seeds, bump, and programId
        (address pda, bytes1 _bump) = try_find_program_address(["newseed_pda"], type(sendTokens).program_id);

        // Verify that the bump passed to the constructor matches the bump derived from the seeds and programId
        // This ensures that only the canonical pda address can be used to create the account (first bump that generates a valid pda address)
        require(bump == _bump, 'INVALID_BUMP');
        // Fund the pda account with additional lamports
        SystemInstruction.transfer(
            tx.accounts.payer.key, // from
            address(this), // to (the address of the account being created)
            fundLamports // amount of lamports to transfer
        );
    }

  
    // Transfer tokens from one token account to another via Cross Program Invocation to Token Program
    function transferTokensToUser(
        address dataAccount,
        address from,
        address to, // token account to transfer to
        uint64 amount // amount to transfer
    ) public {
        // The account required by the switchPower instruction.
        // This is the data account created by the token program (not this program), which stores the state of the switch.
        AccountMeta[1] metas = [
            AccountMeta({pubkey: dataAccount, is_writable: true, is_signer: false})
        ];


        // Invoke the switchPower instruction on the token program.
        tokenProgram.transferTokens{accounts: metas}(from, to, amount);
    }
}

And this is the summarized version of the testing that fails:

  // Configure the client to use the local cluster.
  const provider = anchor.AnchorProvider.env();
  anchor.setProvider(provider);

  // Create address for data account of the:
  const dataAccountToken = anchor.web3.Keypair.generate(); // Token program
  const dataAccountSendTokens = anchor.web3.Keypair.generate(); // SendTokens program

  const mintKeypair = anchor.web3.Keypair.generate();
  const deployerWallet = provider.wallet as anchor.Wallet;
  const connection = provider.connection;

  // Get the programs
  const programToken = anchor.workspace.Token as Program<Token>;
  const programSendTokens = anchor.workspace.SendTokens as Program<SendTokens>;

  // Token info
  const nftTitle = "Homer NFT";
  const nftSymbol = "HOMR";
  const nftUri =
    "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/nft.json";

  // Amount of additional lamports to fund the dataAccount with.
  const fundLamports = 1 * anchor.web3.LAMPORTS_PER_SOL;

  // Derive the PDA that will be used to initialize the dataAccount.
  const [dataAccountPDA, bump] = PublicKey.findProgramAddressSync(
    [Buffer.from("newseed_pda")],
    programSendTokens.programId
  );
  /* ************************************
                    TESTS
 ************************************ */
  it("Is initialized!", async () => {
    // Creates data account for the Token program
    const tx = await programToken.methods
      .new()
      .accounts({ dataAccount: dataAccountToken.publicKey })
      .signers([dataAccountToken])
      .rpc();
    console.log("Your transaction signature", tx);

    // Creates PDA account for the SendTokens program
    const second_tx = await programSendTokens.methods
      .new([bump], new anchor.BN(fundLamports))
      .accounts({ dataAccount: dataAccountPDA })
      .rpc();
    console.log("Your transaction signature", second_tx);

    const accountInfo = await connection.getAccountInfo(dataAccountPDA);
    console.log("AccountInfo Lamports:", accountInfo.lamports);
  });

...
it("Transfer some tokens from wallet to wallet!", async () => {
    //Wallet's associated token account address for mint
    const tokenAccount = await getOrCreateAssociatedTokenAccount(
      connection,
      deployerWallet.payer, // payer
      mintKeypair.publicKey, // mint
      deployerWallet.publicKey // owner
    );

    const receipient = anchor.web3.Keypair.generate();
    const receipientTokenAccount = await getOrCreateAssociatedTokenAccount(
      connection,
      deployerWallet.payer, // payer
      mintKeypair.publicKey, // mint account
      receipient.publicKey // owner account
    );

    const tx = await programSendTokens.methods
      .transferTokensToUser(
        dataAccountPDA,
        dataAccountToken.publicKey,
        receipientTokenAccount.address,
        new anchor.BN(150)
      )
      .accounts({ dataAccount: dataAccountToken.publicKey })
      .remainingAccounts([
        {
          pubkey: deployerWallet.publicKey,
          isWritable: true,
          isSigner: true,
        },
        {
          pubkey: mintKeypair.publicKey,
          isWritable: false,
          isSigner: false,
        },
        {
          pubkey: dataAccountPDA,
          isWritable: true,
          isSigner: false,
        },
        {
          pubkey: tokenAccount.address,
          isWritable: true,
          isSigner: false,
        },
        {
          pubkey: receipientTokenAccount.address,
          isWritable: true,
          isSigner: false,
        },
      ])
      .rpc();
    console.log("Your transaction signature", tx);
  });

1 Answer 1

0

One thing that comes to mind is that does the PDA has the authority/funds to process the transfer.

Please can you push the complete workspace to a GitHub repo and drop the link for check here instead of just the snippet because there could be other issues since the transfer was successful when done directly but not when done via a PDA.

This will help to replicate the problem much more easily and suggest a fix or walk around.

7
  • Yes of course, thank you so much! Here it is: github.com/JoaoMorais96/Solang-Try-Token Sep 11, 2023 at 8:35
  • Okay, I just saw your response today. I will work on the repo you sent above and get back to you
    – Foskaay
    Sep 12, 2023 at 21:10
  • Thank you so much! I'm stuck Sep 13, 2023 at 7:58
  • Sorry to bother, but did you menage the time to look at it? I'm still loss Sep 23, 2023 at 15:56
  • Yeah was able to check but couldn't dwell much on it majorly because am currently working on a Solang Solidity for Solana beginner course to be released soon. Just some note for you though: 1. Based on the repo you sent, I will suggest you move to using Anchor for Solang solidity development on Solana to make things easier. 2. I created this test repo to test the pda transfer but not working still and may have some time to recheck it later when am done with the course github.com/dProgrammingUniversity/pda-mint-transfer-authority
    – Foskaay
    Sep 24, 2023 at 17:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.