5

This is similar to Can I Create an Account and Transfer Tokens in the same Transaction? but the 'answer' to that question doesn't actually show how to use the token account created in the first instruction as the destination for the transfer instruction.

I know I can add an instruction to create an associated token account with:

transaction.add(
  createAssociatedTokenAccountInstruction(
      payer.publicKey,
      associatedToken,
      owner,
      mint,
      programId,
      associatedTokenProgramId
  )
)

And add instructions to do a transfer with:

transaction.add(
  createTransferInstruction(
    sourceTokenAccount,
    destinationTokenAccount,
    ownerPublicKey,
    amount,
    multiSigners,
    programId
  )
);

But is it possible to create a token account, and then transfer a token to the newly created destinationTokenAccount, in a single transaction?

Important:

I understand the concept of chaining instructions with .add() but I don't know how to use the ATA made during the account creating instruction in the subsequent transfer instruction.

Please don't post answers showing me how to use method chaining to add multiple instructions. That is not the question I am asking.

The reason I'm asking is:

  • It would be useful to have a single transaction succeed or fail (rather than, say, paying to make a USDC account for someone in one transaction, then failing to send them money in the next).
  • It may be faster to perform all instructions in a single transaction
  • I want to calculate fees in advance, and one transaction is simpler to calculate fees for than multiple transactions.

2 Answers 2

4

You can get the address for the token account even if the token account doesn't exist yet.

You would add both instructions to the same transaction. With token accounts, you would first need to check if the token account already exists before adding the instruction to create the token account to the transaction.

Here's a reference using @solana/spl-token and @solana/web3.js

  const connection = new Connection(clusterApiUrl(...))

  // Assume sender is Keypair that already has a token account with a balance from MINT
  const sender = Keypair.fromSecretKey(...)

  // Generate a new keypair to represent the receiver
  const receiver = Keypair.generate()

  // The MINT address of token to transfer
  const MINT = new PublicKey(...)

  // Get the mint data (to adjust for decimals for amount)
  const mintData = await getMint(connection, MINT)

  // Get the sender's associated token account address
  const senderTokenAccountAddress = await getAssociatedTokenAddress(
    MINT,
    sender.publicKey
  )

  // Get the receiver's associated token account address
  const receiverTokenAccountAddress = await getAssociatedTokenAddress(
    MINT,
    receiver.publicKey
  )

  // Create a new transaction
  const transaction = new Transaction()

  // Create an instruction to create the receiver's token account if it does not exist
  const createAccountInstruction = createAssociatedTokenAccountInstruction(
    sender.publicKey,
    receiverTokenAccountAddress,
    receiver.publicKey,
    MINT,
    TOKEN_PROGRAM_ID,
    ASSOCIATED_TOKEN_PROGRAM_ID
  )

  // Check if the receiver's token account exists
  let receiverTokenAccount: Account
  try {
    receiverTokenAccount = await getAccount(
      connection,
      receiverTokenAccountAddress,
      "confirmed",
      TOKEN_PROGRAM_ID
    )
  } catch (e) {
    // If the account does not exist, add the create account instruction to the transaction
    transaction.add(createAccountInstruction)
  }

  // Create an instruction to transfer 1 token from the sender's token account to the receiver's token account
  // Adjusting for decimals of the MINT
  const transferInstruction = await createTransferInstruction(
    senderTokenAccountAddress,
    receiverTokenAccountAddress,
    sender.publicKey,
    1 * 10 ** mintData.decimals
  )

  // Add the transfer instruction to the transaction
  transaction.add(transferInstruction)

  // Send the transaction signed by the sender
  const transactionSignature = await connection.sendTransaction(transaction, [
    sender,
  ])
2
  • 1
    You can get the address for the token account even if the token account doesn't exist yet
    – john
    Jan 31, 2023 at 22:10
  • 1
    all good! glad we figured it out
    – john
    Jan 31, 2023 at 22:46
4

The key thing is that token ATA is not fetched from chain, it is calculated, so it can be gotten even if it doesn't exist yet.

Yes you absolutely can, the order of instruction is what matters here. This is what happens when you send any token from your wallet to a new address. First create ATA instruction is passed and then the transfer instruction. The 'associatedToken' variable in this case will be passed in destination. Here's an example code:

const token = new PublicKey(mint);
const transaction = new Transaction();

const fromTokenAccount = await getAssociatedTokenAddress(token, fromPubKey);
const toTokenAccount = await getAssociatedTokenAddress(token, toPubKey);

const toTokenAccountInfo = await connection.getAccountInfo(toTokenAccount);

// Add create token account instruction if source account not created
if (!toTokenAccountInfo) {
  transaction.add(
    createAssociatedTokenAccountInstruction(
      fromPubKey,
      toTokenAccount,
      toPubKey,
      token
    )
  );
}

// Add token transfer instructions to transaction
transaction.add(
  createTransferInstruction(
    fromTokenAccount,
    toTokenAccount,
    fromPubKey,
    Number(amount) * 1000000 // Token decimal (6 in case of USDC)
  )
);
2
  • 1
    it can't be null, it is calculated and not fetched from chain Jan 31, 2023 at 22:29
  • 1
    @mikemaccana done! Jan 31, 2023 at 22:34

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.