1

Let's say updateAccount instruction accepts following accounts

#[account()]
pub payer: Signer<'info>,

#[account(mut), constraint = optional_ata.mint = treasury_ata.mint]
pub optional_ata: Option<Account<'info, TokenAccount>>

Now, if I call this method in ts without using optional account,

await program.methods
      .updateAccount({num: 10})
      .accounts({
          payer: signer.publicKey,
      })

it raises

Error: Invalid arguments: optionalAta not provided.

This method raises

await program.methods
      .updateAccount({num: 10})
      .accounts({
          payer: signer.publicKey,
          optionalAta: null,
      })

this error

Error: Wrong input type for account "optionalAta" in the instruction accounts object for instruction "updateAccount". Expected PublicKey or string.

I am using anchor version 0.26.0 where this update took.

What is the correct way to handle optional accounts when calling the method?

1
  • 2
    I don't know why but this works ``` await program.methods .updateAccount({num: 10}) .accounts({ payer: signer.publicKey, optionalAta: program.programId, }) ```
    – gud
    Commented May 6, 2023 at 7:16

1 Answer 1

3

Use null for the optional accounts:

await program.methods
  .updateAccount({num: 10})
  .accounts({
      payer: signer.publicKey,
      optionalAta: null
  })

Regularly, accounts only accepts Address type which is PublicKey | string. But if isOptional is turned true for the account in IDL, Anchor validates the account as Address | null. What null effectively does is that it replaces the optional account with the program ID.

Here is the logic for the same:

The key to make everything work is the fact that repeated accounts don't add to the size of the transaction due to how Solana serializes accounts in transactions. We use this fact to denote an optional account with the program Id. When the accounts are passed in, if an account should be ignored, clients will send the program Id instead. In addition, with this implementation, adding on optional accounts at the end of an Accounts struct wouldn't be a breaking change and the program will treat the missing account as None (since older clients will not be passing in this new account).

Source: https://github.com/coral-xyz/anchor/pull/2101

In your case, null isn't working because of the type check. So, the perfect solution is to just pass program id because null is eventually going to do the same in the instruction under the hood.

3
  • This still causes error as I mentioned above. The IDL is also correct. { "name": "optionalAta", "isMut": true, "isSigner": false, "isOptional": true },
    – gud
    Commented May 6, 2023 at 7:02
  • Check the updated answer.
    – 0xShuk
    Commented May 6, 2023 at 7:52
  • Thanks for the detailed response!
    – gud
    Commented May 6, 2023 at 8:09

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.