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}