Skip to main content

spl_associated_token_account_interface/
instruction.rs

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