safe_associated_token_account/
instruction.rs

1//! Program instructions
2
3use {
4    crate::{get_associated_token_address_with_program_id, id},
5    assert_matches::assert_matches,
6    borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
7    solana_program::{
8        instruction::{AccountMeta, Instruction},
9        pubkey::Pubkey,
10    },
11};
12
13/// Instructions supported by the AssociatedTokenAccount program
14#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
15pub enum AssociatedTokenAccountInstruction {
16    /// Creates an associated token account for the given wallet address and token mint
17    /// Returns an error if the account exists.
18    ///
19    ///   0. `[writeable,signer]` Funding account (must be a system account)
20    ///   1. `[writeable]` Associated token account address to be created
21    ///   2. `[]` Wallet address for the new associated token account
22    ///   3. `[]` The token mint for the new associated token account
23    ///   4. `[]` System program
24    ///   5. `[]` SPL Token program
25    Create,
26    /// Creates an associated token account for the given wallet address and token mint,
27    /// if it doesn't already exist.  Returns an error if the account exists,
28    /// but with a different owner.
29    ///
30    ///   0. `[writeable,signer]` Funding account (must be a system account)
31    ///   1. `[writeable]` Associated token account address to be created
32    ///   2. `[]` Wallet address for the new associated token account
33    ///   3. `[]` The token mint for the new associated token account
34    ///   4. `[]` System program
35    ///   5. `[]` SPL Token program
36    CreateIdempotent,
37    /// Transfers from and closes a nested associated token account: an
38    /// associated token account owned by an associated token account.
39    ///
40    /// The tokens are moved from the nested associated token account to the
41    /// wallet's associated token account, and the nested account lamports are
42    /// moved to the wallet.
43    ///
44    /// Note: Nested token accounts are an anti-pattern, and almost always
45    /// created unintentionally, so this instruction should only be used to
46    /// recover from errors.
47    ///
48    ///   0. `[writeable]` Nested associated token account, must be owned by `3`
49    ///   1. `[]` Token mint for the nested associated token account
50    ///   2. `[writeable]` Wallet's associated token account
51    ///   3. `[]` Owner associated token account address, must be owned by `5`
52    ///   4. `[]` Token mint for the owner associated token account
53    ///   5. `[writeable, signer]` Wallet address for the owner associated token account
54    ///   6. `[]` SPL Token program
55    RecoverNested,
56}
57
58fn build_associated_token_account_instruction(
59    funding_address: &Pubkey,
60    wallet_address: &Pubkey,
61    token_mint_address: &Pubkey,
62    token_program_id: &Pubkey,
63    instruction: AssociatedTokenAccountInstruction,
64) -> Instruction {
65    let associated_account_address = get_associated_token_address_with_program_id(
66        wallet_address,
67        token_mint_address,
68        token_program_id,
69    );
70    // safety check, assert if not a creation instruction
71    assert_matches!(
72        instruction,
73        AssociatedTokenAccountInstruction::Create
74            | AssociatedTokenAccountInstruction::CreateIdempotent
75    );
76    Instruction {
77        program_id: id(),
78        accounts: vec![
79            AccountMeta::new(*funding_address, true),
80            AccountMeta::new(associated_account_address, false),
81            AccountMeta::new_readonly(*wallet_address, false),
82            AccountMeta::new_readonly(*token_mint_address, false),
83            AccountMeta::new_readonly(solana_program::system_program::id(), false),
84            AccountMeta::new_readonly(*token_program_id, false),
85        ],
86        data: instruction.try_to_vec().unwrap(),
87    }
88}
89
90/// Creates Create instruction
91pub fn create_associated_token_account(
92    funding_address: &Pubkey,
93    wallet_address: &Pubkey,
94    token_mint_address: &Pubkey,
95    token_program_id: &Pubkey,
96) -> Instruction {
97    build_associated_token_account_instruction(
98        funding_address,
99        wallet_address,
100        token_mint_address,
101        token_program_id,
102        AssociatedTokenAccountInstruction::Create,
103    )
104}
105
106/// Creates CreateIdempotent instruction
107pub fn create_associated_token_account_idempotent(
108    funding_address: &Pubkey,
109    wallet_address: &Pubkey,
110    token_mint_address: &Pubkey,
111    token_program_id: &Pubkey,
112) -> Instruction {
113    build_associated_token_account_instruction(
114        funding_address,
115        wallet_address,
116        token_mint_address,
117        token_program_id,
118        AssociatedTokenAccountInstruction::CreateIdempotent,
119    )
120}
121
122/// Creates a `RecoverNested` instruction
123pub fn recover_nested(
124    wallet_address: &Pubkey,
125    owner_token_mint_address: &Pubkey,
126    nested_token_mint_address: &Pubkey,
127    token_program_id: &Pubkey,
128) -> Instruction {
129    let owner_associated_account_address = get_associated_token_address_with_program_id(
130        wallet_address,
131        owner_token_mint_address,
132        token_program_id,
133    );
134    let destination_associated_account_address = get_associated_token_address_with_program_id(
135        wallet_address,
136        nested_token_mint_address,
137        token_program_id,
138    );
139    let nested_associated_account_address = get_associated_token_address_with_program_id(
140        &owner_associated_account_address, // ATA is wrongly used as a wallet_address
141        nested_token_mint_address,
142        token_program_id,
143    );
144
145    let instruction_data = AssociatedTokenAccountInstruction::RecoverNested;
146
147    Instruction {
148        program_id: id(),
149        accounts: vec![
150            AccountMeta::new(nested_associated_account_address, false),
151            AccountMeta::new_readonly(*nested_token_mint_address, false),
152            AccountMeta::new(destination_associated_account_address, false),
153            AccountMeta::new_readonly(owner_associated_account_address, false),
154            AccountMeta::new_readonly(*owner_token_mint_address, false),
155            AccountMeta::new(*wallet_address, true),
156            AccountMeta::new_readonly(*token_program_id, false),
157        ],
158        data: instruction_data.try_to_vec().unwrap(),
159    }
160}