spl_token_wrap/
instruction.rs

1//! Program instructions
2
3use {
4    solana_instruction::{AccountMeta, Instruction},
5    solana_program_error::ProgramError,
6    solana_pubkey::Pubkey,
7    std::convert::TryInto,
8};
9
10/// Instructions supported by the Token Wrap program
11#[derive(Clone, Debug, PartialEq)]
12#[repr(u8)]
13pub enum TokenWrapInstruction {
14    /// Create a wrapped token mint. Assumes caller has pre-funded wrapped mint
15    /// and backpointer account. Supports both directions:
16    /// - spl-token to token-2022
17    /// - token-2022 to spl-token
18    /// - token-2022 to token-2022 w/ new extensions
19    ///
20    /// Accounts expected by this instruction:
21    ///
22    /// 0. `[w]` Unallocated wrapped mint account to create (PDA), address must
23    ///    be: `get_wrapped_mint_address(unwrapped_mint_address,
24    ///    wrapped_token_program_id)`
25    /// 1. `[w]` Unallocated wrapped backpointer account to create (PDA)
26    ///    `get_wrapped_mint_backpointer_address(wrapped_mint_address)`
27    /// 2. `[]` Existing unwrapped mint
28    /// 3. `[]` System program
29    /// 4. `[]` SPL Token program for wrapped mint
30    CreateMint {
31        /// If true, idempotent creation. If false, fail if the mint already
32        /// exists.
33        idempotent: bool,
34    },
35
36    /// Wrap tokens
37    ///
38    /// Move a user's unwrapped tokens into an escrow account and mint the same
39    /// number of wrapped tokens into the provided account.
40    ///
41    /// Accounts expected by this instruction:
42    ///
43    /// 0. `[w]` Recipient wrapped token account
44    /// 1. `[w]` Wrapped mint, must be initialized, address must be:
45    ///    `get_wrapped_mint_address(unwrapped_mint_address,
46    ///    wrapped_token_program_id)`
47    /// 2. `[]` Wrapped mint authority, address must be:
48    ///    `get_wrapped_mint_authority(wrapped_mint)`
49    /// 3. `[]` SPL Token program for unwrapped mint
50    /// 4. `[]` SPL Token program for wrapped mint
51    /// 5. `[w]` Unwrapped token account to wrap
52    ///    `get_wrapped_mint_authority(wrapped_mint_address)`
53    /// 6. `[]` Unwrapped token mint
54    /// 7. `[w]` Escrow of unwrapped tokens, address must be an `ATA`:
55    ///    `get_escrow_address(unwrapped_mint, unwrapped_token_program,
56    ///    wrapped_token_program)`
57    /// 8. `[s]` Transfer authority on unwrapped token account. Not required to
58    ///    be a signer if it's a multisig.
59    /// 9. `..8+M` `[s]` (Optional) M multisig signers on unwrapped token
60    ///    account.
61    Wrap {
62        /// little-endian `u64` representing the amount to wrap
63        amount: u64,
64    },
65
66    /// Unwrap tokens
67    ///
68    /// Burn user wrapped tokens and transfer the same amount of unwrapped
69    /// tokens from the escrow account to the provided account.
70    ///
71    /// Accounts expected by this instruction:
72    /// 0. `[w]` Escrow of unwrapped tokens, address must be an `ATA`:
73    ///    `get_escrow_address(unwrapped_mint, unwrapped_token_program,
74    ///    wrapped_token_program)`
75    /// 1. `[w]` Recipient unwrapped tokens
76    /// 2. `[]` Wrapped mint authority, address must be:
77    ///    `get_wrapped_mint_authority(wrapped_mint)`
78    /// 3. `[]` Unwrapped token mint
79    /// 4. `[]` SPL Token program for wrapped mint
80    /// 5. `[]` SPL Token program for unwrapped mint
81    /// 6. `[w]` Wrapped token account to unwrap
82    /// 7. `[w]` Wrapped mint, address must be:
83    ///    `get_wrapped_mint_address(unwrapped_mint_address,
84    ///    wrapped_token_program_id)`
85    /// 8. `[s]` Transfer authority on wrapped token account
86    /// 9. `..8+M` `[s]` (Optional) M multisig signers on wrapped token account
87    Unwrap {
88        /// little-endian `u64` representing the amount to unwrap
89        amount: u64,
90    },
91
92    /// Closes a stuck escrow `ATA`. This is for the edge case where an
93    /// unwrapped mint with a close authority is closed and then a new mint
94    /// is created at the same address but with a different size, leaving
95    /// the escrow `ATA` in a bad state.
96    ///
97    /// This instruction will close the old escrow `ATA`, returning the lamports
98    /// to the destination account. It will only work if the current escrow has
99    /// different extensions than the mint. The client is then responsible
100    /// for calling `create_associated_token_account` to recreate it.
101    ///
102    /// Accounts expected by this instruction:
103    ///
104    /// 0. `[w]` Escrow account to close (`ATA`)
105    /// 1. `[w]` Destination for lamports from closed account
106    /// 2. `[]` Unwrapped mint
107    /// 3. `[]` Wrapped mint
108    /// 4. `[]` Wrapped mint authority (PDA)
109    /// 5. `[]` Token-2022 program
110    CloseStuckEscrow,
111
112    /// This instruction copies the metadata fields from an unwrapped mint to
113    /// its wrapped mint `TokenMetadata` extension.
114    ///
115    /// Supports (unwrapped to wrapped):
116    /// - Token-2022 to Token-2022
117    /// - SPL-token to Token-2022
118    ///
119    /// If source mint is a Token-2022, it must have a `MetadataPointer` and the
120    /// account it points to must be provided. If source mint is an SPL-Token,
121    /// the `Metaplex` PDA must be provided.
122    ///
123    /// If the `TokenMetadata` extension on the wrapped mint if not present, it
124    /// will initialize it. The client is responsible for funding the wrapped
125    /// mint account with enough lamports to cover the rent for the
126    /// additional space required by the `TokenMetadata` extension and/or
127    /// metadata sync.
128    ///
129    /// Accounts expected by this instruction:
130    ///
131    /// 0. `[w]` Wrapped mint
132    /// 1. `[]` Wrapped mint authority PDA
133    /// 2. `[]` Unwrapped mint
134    /// 3. `[]` Token-2022 program
135    /// 4. `[]` (Optional) Source metadata account. Required if metadata pointer
136    ///    indicates external account.
137    /// 5. `[]` (Optional) Owner program. Required when metadata account is
138    ///    owned by a third-party program.
139    SyncMetadataToToken2022,
140
141    /// This instruction copies the metadata fields from an unwrapped mint to
142    /// its wrapped mint `Metaplex` metadata account.
143    ///
144    /// Supports (unwrapped to wrapped):
145    /// - Token-2022 to SPL-token
146    /// - SPL-token to SPL-token
147    ///
148    /// This instruction will create the `Metaplex` metadata account if it
149    /// doesn't exist, or update it if it does. The `wrapped_mint_authority`
150    /// PDA must be pre-funded with enough lamports to cover the rent for
151    /// the `Metaplex` metadata account's creation or updates, as it will
152    /// act as the payer for the `Metaplex` program CPI.
153    ///
154    /// If source mint is a Token-2022, it must have a `MetadataPointer` and the
155    /// account it points to must be provided. If source mint is an SPL-Token,
156    /// the `Metaplex` PDA must be provided.
157    ///
158    /// Accounts expected by this instruction:
159    ///
160    /// 0. `[w]` `Metaplex` metadata account
161    /// 1. `[w]` Wrapped mint authority (PDA)
162    /// 2. `[]` Wrapped SPL Token mint
163    /// 3. `[]` Unwrapped mint
164    /// 4. `[]` `Metaplex` Token Metadata Program
165    /// 5. `[]` System program
166    /// 6. `[]` Rent sysvar
167    /// 7. `[]` (Optional) Source metadata account. Required if unwrapped mint
168    ///    is an SPL-Token or, if a Token-2022, its metadata pointer indicates
169    ///    an external account.
170    /// 8. `[]` (Optional) Owner program. Required when metadata account is
171    ///    owned by a third-party program.
172    SyncMetadataToSplToken,
173}
174
175impl TokenWrapInstruction {
176    /// Packs a [`TokenWrapInstruction`](enum.TokenWrapInstruction.html) into a
177    /// byte array.
178    pub fn pack(&self) -> Vec<u8> {
179        let mut buf = Vec::new();
180        match self {
181            TokenWrapInstruction::CreateMint { idempotent } => {
182                buf.push(0);
183                buf.push(if *idempotent { 1 } else { 0 });
184            }
185
186            TokenWrapInstruction::Wrap { amount } => {
187                buf.push(1);
188                buf.extend_from_slice(&amount.to_le_bytes());
189            }
190            TokenWrapInstruction::Unwrap { amount } => {
191                buf.push(2);
192                buf.extend_from_slice(&amount.to_le_bytes());
193            }
194            TokenWrapInstruction::CloseStuckEscrow => {
195                buf.push(3);
196            }
197            TokenWrapInstruction::SyncMetadataToToken2022 => {
198                buf.push(4);
199            }
200            TokenWrapInstruction::SyncMetadataToSplToken => {
201                buf.push(5);
202            }
203        }
204        buf
205    }
206
207    /// Unpacks a byte array into a
208    /// [`TokenWrapInstruction`](enum.TokenWrapInstruction.html).
209    pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
210        match input.split_first() {
211            Some((&0, rest)) if rest.len() == 1 => {
212                let idempotent = match rest[0] {
213                    0 => false,
214                    1 => true,
215                    _ => return Err(ProgramError::InvalidInstructionData),
216                };
217                Ok(TokenWrapInstruction::CreateMint { idempotent })
218            }
219            Some((&1, rest)) if rest.len() == 8 => {
220                let amount = u64::from_le_bytes(rest.try_into().unwrap());
221                Ok(TokenWrapInstruction::Wrap { amount })
222            }
223            Some((&2, rest)) if rest.len() == 8 => {
224                let amount = u64::from_le_bytes(rest.try_into().unwrap());
225                Ok(TokenWrapInstruction::Unwrap { amount })
226            }
227            Some((&3, [])) => Ok(TokenWrapInstruction::CloseStuckEscrow),
228            Some((&4, [])) => Ok(TokenWrapInstruction::SyncMetadataToToken2022),
229            Some((&5, [])) => Ok(TokenWrapInstruction::SyncMetadataToSplToken),
230            _ => Err(ProgramError::InvalidInstructionData),
231        }
232    }
233}
234
235/// Creates `CreateMint` instruction.
236pub fn create_mint(
237    program_id: &Pubkey,
238    wrapped_mint_address: &Pubkey,
239    wrapped_backpointer_address: &Pubkey,
240    unwrapped_mint_address: &Pubkey,
241    wrapped_token_program_id: &Pubkey,
242    idempotent: bool,
243) -> Instruction {
244    let accounts = vec![
245        AccountMeta::new(*wrapped_mint_address, false),
246        AccountMeta::new(*wrapped_backpointer_address, false),
247        AccountMeta::new_readonly(*unwrapped_mint_address, false),
248        AccountMeta::new_readonly(solana_system_interface::program::id(), false),
249        AccountMeta::new_readonly(*wrapped_token_program_id, false),
250    ];
251    let data = TokenWrapInstruction::CreateMint { idempotent }.pack();
252    Instruction::new_with_bytes(*program_id, &data, accounts)
253}
254
255/// Creates `Wrap` instruction.
256#[allow(clippy::too_many_arguments)]
257pub fn wrap(
258    program_id: &Pubkey,
259    recipient_wrapped_token_account_address: &Pubkey,
260    wrapped_mint_address: &Pubkey,
261    wrapped_mint_authority_address: &Pubkey,
262    unwrapped_token_program_id: &Pubkey,
263    wrapped_token_program_id: &Pubkey,
264    unwrapped_token_account_address: &Pubkey,
265    unwrapped_mint_address: &Pubkey,
266    unwrapped_escrow_address: &Pubkey,
267    transfer_authority_address: &Pubkey,
268    multisig_signer_pubkeys: &[&Pubkey],
269    amount: u64,
270) -> Instruction {
271    let mut accounts = vec![
272        AccountMeta::new(*recipient_wrapped_token_account_address, false),
273        AccountMeta::new(*wrapped_mint_address, false),
274        AccountMeta::new_readonly(*wrapped_mint_authority_address, false),
275        AccountMeta::new_readonly(*unwrapped_token_program_id, false),
276        AccountMeta::new_readonly(*wrapped_token_program_id, false),
277        AccountMeta::new(*unwrapped_token_account_address, false),
278        AccountMeta::new_readonly(*unwrapped_mint_address, false),
279        AccountMeta::new(*unwrapped_escrow_address, false),
280        AccountMeta::new_readonly(
281            *transfer_authority_address,
282            multisig_signer_pubkeys.is_empty(),
283        ),
284    ];
285    for signer_pubkey in multisig_signer_pubkeys.iter() {
286        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
287    }
288
289    let data = TokenWrapInstruction::Wrap { amount }.pack();
290    Instruction::new_with_bytes(*program_id, &data, accounts)
291}
292
293/// Creates `Unwrap` instruction.
294#[allow(clippy::too_many_arguments)]
295pub fn unwrap(
296    program_id: &Pubkey,
297    unwrapped_escrow_address: &Pubkey,
298    recipient_unwrapped_token_account_address: &Pubkey,
299    wrapped_mint_authority_address: &Pubkey,
300    unwrapped_mint_address: &Pubkey,
301    wrapped_token_program_id: &Pubkey,
302    unwrapped_token_program_id: &Pubkey,
303    wrapped_token_account_address: &Pubkey,
304    wrapped_mint_address: &Pubkey,
305    transfer_authority_address: &Pubkey,
306    multisig_signer_pubkeys: &[&Pubkey],
307    amount: u64,
308) -> Instruction {
309    let mut accounts = vec![
310        AccountMeta::new(*unwrapped_escrow_address, false),
311        AccountMeta::new(*recipient_unwrapped_token_account_address, false),
312        AccountMeta::new_readonly(*wrapped_mint_authority_address, false),
313        AccountMeta::new_readonly(*unwrapped_mint_address, false),
314        AccountMeta::new_readonly(*wrapped_token_program_id, false),
315        AccountMeta::new_readonly(*unwrapped_token_program_id, false),
316        AccountMeta::new(*wrapped_token_account_address, false),
317        AccountMeta::new(*wrapped_mint_address, false),
318        AccountMeta::new_readonly(
319            *transfer_authority_address,
320            multisig_signer_pubkeys.is_empty(),
321        ),
322    ];
323    for signer_pubkey in multisig_signer_pubkeys.iter() {
324        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
325    }
326
327    let data = TokenWrapInstruction::Unwrap { amount }.pack();
328    Instruction::new_with_bytes(*program_id, &data, accounts)
329}
330
331/// Creates `CloseStuckEscrow` instruction.
332pub fn close_stuck_escrow(
333    program_id: &Pubkey,
334    escrow_address: &Pubkey,
335    destination_address: &Pubkey,
336    unwrapped_mint_address: &Pubkey,
337    wrapped_mint_address: &Pubkey,
338    wrapped_mint_authority_address: &Pubkey,
339) -> Instruction {
340    let accounts = vec![
341        AccountMeta::new(*escrow_address, false),
342        AccountMeta::new(*destination_address, false),
343        AccountMeta::new_readonly(*unwrapped_mint_address, false),
344        AccountMeta::new_readonly(*wrapped_mint_address, false),
345        AccountMeta::new_readonly(*wrapped_mint_authority_address, false),
346        AccountMeta::new_readonly(spl_token_2022::id(), false),
347    ];
348    let data = TokenWrapInstruction::CloseStuckEscrow.pack();
349    Instruction::new_with_bytes(*program_id, &data, accounts)
350}
351
352/// Creates `SyncMetadataToToken2022` instruction.
353pub fn sync_metadata_to_token_2022(
354    program_id: &Pubkey,
355    wrapped_mint: &Pubkey,
356    wrapped_mint_authority: &Pubkey,
357    unwrapped_mint: &Pubkey,
358    source_metadata: Option<&Pubkey>,
359    owner_program: Option<&Pubkey>,
360) -> Instruction {
361    let mut accounts = vec![
362        AccountMeta::new(*wrapped_mint, false),
363        AccountMeta::new_readonly(*wrapped_mint_authority, false),
364        AccountMeta::new_readonly(*unwrapped_mint, false),
365        AccountMeta::new_readonly(spl_token_2022::id(), false),
366    ];
367
368    if let Some(pubkey) = source_metadata {
369        accounts.push(AccountMeta::new_readonly(*pubkey, false));
370    }
371
372    if let Some(owner) = owner_program {
373        accounts.push(AccountMeta::new_readonly(*owner, false));
374    }
375
376    let data = TokenWrapInstruction::SyncMetadataToToken2022.pack();
377    Instruction::new_with_bytes(*program_id, &data, accounts)
378}
379
380/// Creates `SyncMetadataToSplToken` instruction.
381pub fn sync_metadata_to_spl_token(
382    program_id: &Pubkey,
383    metaplex_metadata: &Pubkey,
384    wrapped_mint_authority: &Pubkey,
385    wrapped_mint: &Pubkey,
386    unwrapped_mint: &Pubkey,
387    source_metadata: Option<&Pubkey>,
388    owner_program: Option<&Pubkey>,
389) -> Instruction {
390    let mut accounts = vec![
391        AccountMeta::new(*metaplex_metadata, false),
392        AccountMeta::new(*wrapped_mint_authority, false),
393        AccountMeta::new_readonly(*wrapped_mint, false),
394        AccountMeta::new_readonly(*unwrapped_mint, false),
395        AccountMeta::new_readonly(mpl_token_metadata::ID, false),
396        AccountMeta::new_readonly(solana_system_interface::program::id(), false),
397        AccountMeta::new_readonly(solana_sysvar::rent::id(), false),
398    ];
399
400    if let Some(pubkey) = source_metadata {
401        accounts.push(AccountMeta::new_readonly(*pubkey, false));
402    }
403
404    if let Some(owner) = owner_program {
405        accounts.push(AccountMeta::new_readonly(*owner, false));
406    }
407
408    let data = TokenWrapInstruction::SyncMetadataToSplToken.pack();
409    Instruction::new_with_bytes(*program_id, &data, accounts)
410}