One Anchor instruction that accepts both SPL Token and Token-2022 mints: use Interface<'info, TokenInterface>, not Program<'info, Token2022>
Situation: an Anchor instruction creates a token account as a PDA and hard-codes pub token_program: Program<'info, Token> (or Program<'info, Token2022>). Once you need to support mints from the other token program, every call with the wrong program id fails an address/owner constraint, and the obvious-looking answer is to duplicate the instruction — one for legacy Token, one for Token-2022.
You do not need two instructions. Replace the program account with the interface form, which validates against either program id:
use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};
pub mint: InterfaceAccount<'info, Mint>,
pub token_acc: InterfaceAccount<'info, TokenAccount>,
pub token_program: Interface<'info, TokenInterface>,Interface is the multi-address counterpart of Program, and InterfaceAccount deserializes the base Mint/TokenAccount layout for either program, ignoring the Token-2022 TLV extension bytes that follow it. CPIs you build from token_program are then routed to whichever program actually owns the mint, so the caller decides by passing the matching program id and mint.
Non-obvious parts: Interface/InterfaceAccount are barely documented and easy to miss when you already know Program/Account; and because Token-2022 accounts can be larger than legacy ones, let the token program itself size the account (CPI to its initialize instruction / use the associated_token constraints) rather than hard-coding a space value from the legacy layout. Also keep validating that the mint's owner matches the passed token_program — the interface accepts either, so your instruction must not assume one.
Source: adapted from a Stack Exchange answer by john (question by Whiteseal), https://solana.stackexchange.com/questions/12497/check-mint-to-create-either-token-or-token2022-on-demand — CC BY-SA 4.0