spl-token-wrap 1.0.0

Solana Program Library Token Wrap
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
//! Program instructions

use {
    solana_instruction::{AccountMeta, Instruction},
    solana_program_error::ProgramError,
    solana_pubkey::Pubkey,
    std::convert::TryInto,
};

/// Instructions supported by the Token Wrap program
#[derive(Clone, Debug, PartialEq)]
#[repr(u8)]
pub enum TokenWrapInstruction {
    /// Create a wrapped token mint. Assumes caller has pre-funded wrapped mint
    /// and backpointer account. Supports both directions:
    /// - spl-token to token-2022
    /// - token-2022 to spl-token
    /// - token-2022 to token-2022 w/ new extensions
    ///
    /// Accounts expected by this instruction:
    ///
    /// 0. `[w]` Unallocated wrapped mint account to create (PDA), address must
    ///    be: `get_wrapped_mint_address(unwrapped_mint_address,
    ///    wrapped_token_program_id)`
    /// 1. `[w]` Unallocated wrapped backpointer account to create (PDA)
    ///    `get_wrapped_mint_backpointer_address(wrapped_mint_address)`
    /// 2. `[]` Existing unwrapped mint
    /// 3. `[]` System program
    /// 4. `[]` SPL Token program for wrapped mint
    CreateMint {
        /// If true, idempotent creation. If false, fail if the mint already
        /// exists.
        idempotent: bool,
    },

    /// Wrap tokens
    ///
    /// Move a user's unwrapped tokens into an escrow account and mint the same
    /// number of wrapped tokens into the provided account.
    ///
    /// Accounts expected by this instruction:
    ///
    /// 0. `[w]` Recipient wrapped token account
    /// 1. `[w]` Wrapped mint, must be initialized, address must be:
    ///    `get_wrapped_mint_address(unwrapped_mint_address,
    ///    wrapped_token_program_id)`
    /// 2. `[]` Wrapped mint authority, address must be:
    ///    `get_wrapped_mint_authority(wrapped_mint)`
    /// 3. `[]` SPL Token program for unwrapped mint
    /// 4. `[]` SPL Token program for wrapped mint
    /// 5. `[w]` Unwrapped token account to wrap
    ///    `get_wrapped_mint_authority(wrapped_mint_address)`
    /// 6. `[]` Unwrapped token mint
    /// 7. `[w]` Escrow of unwrapped tokens, address must be an `ATA`:
    ///    `get_escrow_address(unwrapped_mint, unwrapped_token_program,
    ///    wrapped_token_program)`
    /// 8. `[s]` Transfer authority on unwrapped token account. Not required to
    ///    be a signer if it's a multisig.
    /// 9. `..8+M` `[s]` (Optional) M multisig signers on unwrapped token
    ///    account.
    Wrap {
        /// little-endian `u64` representing the amount to wrap
        amount: u64,
    },

    /// Unwrap tokens
    ///
    /// Burn user wrapped tokens and transfer the same amount of unwrapped
    /// tokens from the escrow account to the provided account.
    ///
    /// Accounts expected by this instruction:
    /// 0. `[w]` Escrow of unwrapped tokens, address must be an `ATA`:
    ///    `get_escrow_address(unwrapped_mint, unwrapped_token_program,
    ///    wrapped_token_program)`
    /// 1. `[w]` Recipient unwrapped tokens
    /// 2. `[]` Wrapped mint authority, address must be:
    ///    `get_wrapped_mint_authority(wrapped_mint)`
    /// 3. `[]` Unwrapped token mint
    /// 4. `[]` SPL Token program for wrapped mint
    /// 5. `[]` SPL Token program for unwrapped mint
    /// 6. `[w]` Wrapped token account to unwrap
    /// 7. `[w]` Wrapped mint, address must be:
    ///    `get_wrapped_mint_address(unwrapped_mint_address,
    ///    wrapped_token_program_id)`
    /// 8. `[s]` Transfer authority on wrapped token account
    /// 9. `..8+M` `[s]` (Optional) M multisig signers on wrapped token account
    Unwrap {
        /// little-endian `u64` representing the amount to unwrap
        amount: u64,
    },

    /// Closes a stuck escrow `ATA`. This is for the edge case where an
    /// unwrapped mint with a close authority is closed and then a new mint
    /// is created at the same address but with a different size, leaving
    /// the escrow `ATA` in a bad state.
    ///
    /// This instruction will close the old escrow `ATA`, returning the lamports
    /// to the destination account. It will only work if the current escrow has
    /// different extensions than the mint. The client is then responsible
    /// for calling `create_associated_token_account` to recreate it.
    ///
    /// Accounts expected by this instruction:
    ///
    /// 0. `[w]` Escrow account to close (`ATA`)
    /// 1. `[w]` Destination for lamports from closed account
    /// 2. `[]` Unwrapped mint
    /// 3. `[]` Wrapped mint
    /// 4. `[]` Wrapped mint authority (PDA)
    /// 5. `[]` Token-2022 program
    CloseStuckEscrow,

    /// This instruction copies the metadata fields from an unwrapped mint to
    /// its wrapped mint `TokenMetadata` extension.
    ///
    /// Supports (unwrapped to wrapped):
    /// - Token-2022 to Token-2022
    /// - SPL-token to Token-2022
    ///
    /// If source mint is a Token-2022, it must have a `MetadataPointer` and the
    /// account it points to must be provided. If source mint is an SPL-Token,
    /// the `Metaplex` PDA must be provided.
    ///
    /// If the `TokenMetadata` extension on the wrapped mint if not present, it
    /// will initialize it. The client is responsible for funding the wrapped
    /// mint account with enough lamports to cover the rent for the
    /// additional space required by the `TokenMetadata` extension and/or
    /// metadata sync.
    ///
    /// Accounts expected by this instruction:
    ///
    /// 0. `[w]` Wrapped mint
    /// 1. `[]` Wrapped mint authority PDA
    /// 2. `[]` Unwrapped mint
    /// 3. `[]` Token-2022 program
    /// 4. `[]` (Optional) Source metadata account. Required if metadata pointer
    ///    indicates external account.
    /// 5. `[]` (Optional) Owner program. Required when metadata account is
    ///    owned by a third-party program.
    SyncMetadataToToken2022,

    /// This instruction copies the metadata fields from an unwrapped mint to
    /// its wrapped mint `Metaplex` metadata account.
    ///
    /// Supports (unwrapped to wrapped):
    /// - Token-2022 to SPL-token
    /// - SPL-token to SPL-token
    ///
    /// This instruction will create the `Metaplex` metadata account if it
    /// doesn't exist, or update it if it does. The `wrapped_mint_authority`
    /// PDA must be pre-funded with enough lamports to cover the rent for
    /// the `Metaplex` metadata account's creation or updates, as it will
    /// act as the payer for the `Metaplex` program CPI.
    ///
    /// If source mint is a Token-2022, it must have a `MetadataPointer` and the
    /// account it points to must be provided. If source mint is an SPL-Token,
    /// the `Metaplex` PDA must be provided.
    ///
    /// Accounts expected by this instruction:
    ///
    /// 0. `[w]` `Metaplex` metadata account
    /// 1. `[w]` Wrapped mint authority (PDA)
    /// 2. `[]` Wrapped SPL Token mint
    /// 3. `[]` Unwrapped mint
    /// 4. `[]` `Metaplex` Token Metadata Program
    /// 5. `[]` System program
    /// 6. `[]` Rent sysvar
    /// 7. `[]` (Optional) Source metadata account. Required if unwrapped mint
    ///    is an SPL-Token or, if a Token-2022, its metadata pointer indicates
    ///    an external account.
    /// 8. `[]` (Optional) Owner program. Required when metadata account is
    ///    owned by a third-party program.
    SyncMetadataToSplToken,
}

impl TokenWrapInstruction {
    /// Packs a [`TokenWrapInstruction`](enum.TokenWrapInstruction.html) into a
    /// byte array.
    pub fn pack(&self) -> Vec<u8> {
        let mut buf = Vec::new();
        match self {
            TokenWrapInstruction::CreateMint { idempotent } => {
                buf.push(0);
                buf.push(if *idempotent { 1 } else { 0 });
            }

            TokenWrapInstruction::Wrap { amount } => {
                buf.push(1);
                buf.extend_from_slice(&amount.to_le_bytes());
            }
            TokenWrapInstruction::Unwrap { amount } => {
                buf.push(2);
                buf.extend_from_slice(&amount.to_le_bytes());
            }
            TokenWrapInstruction::CloseStuckEscrow => {
                buf.push(3);
            }
            TokenWrapInstruction::SyncMetadataToToken2022 => {
                buf.push(4);
            }
            TokenWrapInstruction::SyncMetadataToSplToken => {
                buf.push(5);
            }
        }
        buf
    }

    /// Unpacks a byte array into a
    /// [`TokenWrapInstruction`](enum.TokenWrapInstruction.html).
    pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
        match input.split_first() {
            Some((&0, rest)) if rest.len() == 1 => {
                let idempotent = match rest[0] {
                    0 => false,
                    1 => true,
                    _ => return Err(ProgramError::InvalidInstructionData),
                };
                Ok(TokenWrapInstruction::CreateMint { idempotent })
            }
            Some((&1, rest)) if rest.len() == 8 => {
                let amount = u64::from_le_bytes(rest.try_into().unwrap());
                Ok(TokenWrapInstruction::Wrap { amount })
            }
            Some((&2, rest)) if rest.len() == 8 => {
                let amount = u64::from_le_bytes(rest.try_into().unwrap());
                Ok(TokenWrapInstruction::Unwrap { amount })
            }
            Some((&3, [])) => Ok(TokenWrapInstruction::CloseStuckEscrow),
            Some((&4, [])) => Ok(TokenWrapInstruction::SyncMetadataToToken2022),
            Some((&5, [])) => Ok(TokenWrapInstruction::SyncMetadataToSplToken),
            _ => Err(ProgramError::InvalidInstructionData),
        }
    }
}

/// Creates `CreateMint` instruction.
pub fn create_mint(
    program_id: &Pubkey,
    wrapped_mint_address: &Pubkey,
    wrapped_backpointer_address: &Pubkey,
    unwrapped_mint_address: &Pubkey,
    wrapped_token_program_id: &Pubkey,
    idempotent: bool,
) -> Instruction {
    let accounts = vec![
        AccountMeta::new(*wrapped_mint_address, false),
        AccountMeta::new(*wrapped_backpointer_address, false),
        AccountMeta::new_readonly(*unwrapped_mint_address, false),
        AccountMeta::new_readonly(solana_system_interface::program::id(), false),
        AccountMeta::new_readonly(*wrapped_token_program_id, false),
    ];
    let data = TokenWrapInstruction::CreateMint { idempotent }.pack();
    Instruction::new_with_bytes(*program_id, &data, accounts)
}

/// Creates `Wrap` instruction.
#[allow(clippy::too_many_arguments)]
pub fn wrap(
    program_id: &Pubkey,
    recipient_wrapped_token_account_address: &Pubkey,
    wrapped_mint_address: &Pubkey,
    wrapped_mint_authority_address: &Pubkey,
    unwrapped_token_program_id: &Pubkey,
    wrapped_token_program_id: &Pubkey,
    unwrapped_token_account_address: &Pubkey,
    unwrapped_mint_address: &Pubkey,
    unwrapped_escrow_address: &Pubkey,
    transfer_authority_address: &Pubkey,
    multisig_signer_pubkeys: &[&Pubkey],
    amount: u64,
) -> Instruction {
    let mut accounts = vec![
        AccountMeta::new(*recipient_wrapped_token_account_address, false),
        AccountMeta::new(*wrapped_mint_address, false),
        AccountMeta::new_readonly(*wrapped_mint_authority_address, false),
        AccountMeta::new_readonly(*unwrapped_token_program_id, false),
        AccountMeta::new_readonly(*wrapped_token_program_id, false),
        AccountMeta::new(*unwrapped_token_account_address, false),
        AccountMeta::new_readonly(*unwrapped_mint_address, false),
        AccountMeta::new(*unwrapped_escrow_address, false),
        AccountMeta::new_readonly(
            *transfer_authority_address,
            multisig_signer_pubkeys.is_empty(),
        ),
    ];
    for signer_pubkey in multisig_signer_pubkeys.iter() {
        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
    }

    let data = TokenWrapInstruction::Wrap { amount }.pack();
    Instruction::new_with_bytes(*program_id, &data, accounts)
}

/// Creates `Unwrap` instruction.
#[allow(clippy::too_many_arguments)]
pub fn unwrap(
    program_id: &Pubkey,
    unwrapped_escrow_address: &Pubkey,
    recipient_unwrapped_token_account_address: &Pubkey,
    wrapped_mint_authority_address: &Pubkey,
    unwrapped_mint_address: &Pubkey,
    wrapped_token_program_id: &Pubkey,
    unwrapped_token_program_id: &Pubkey,
    wrapped_token_account_address: &Pubkey,
    wrapped_mint_address: &Pubkey,
    transfer_authority_address: &Pubkey,
    multisig_signer_pubkeys: &[&Pubkey],
    amount: u64,
) -> Instruction {
    let mut accounts = vec![
        AccountMeta::new(*unwrapped_escrow_address, false),
        AccountMeta::new(*recipient_unwrapped_token_account_address, false),
        AccountMeta::new_readonly(*wrapped_mint_authority_address, false),
        AccountMeta::new_readonly(*unwrapped_mint_address, false),
        AccountMeta::new_readonly(*wrapped_token_program_id, false),
        AccountMeta::new_readonly(*unwrapped_token_program_id, false),
        AccountMeta::new(*wrapped_token_account_address, false),
        AccountMeta::new(*wrapped_mint_address, false),
        AccountMeta::new_readonly(
            *transfer_authority_address,
            multisig_signer_pubkeys.is_empty(),
        ),
    ];
    for signer_pubkey in multisig_signer_pubkeys.iter() {
        accounts.push(AccountMeta::new_readonly(**signer_pubkey, true));
    }

    let data = TokenWrapInstruction::Unwrap { amount }.pack();
    Instruction::new_with_bytes(*program_id, &data, accounts)
}

/// Creates `CloseStuckEscrow` instruction.
pub fn close_stuck_escrow(
    program_id: &Pubkey,
    escrow_address: &Pubkey,
    destination_address: &Pubkey,
    unwrapped_mint_address: &Pubkey,
    wrapped_mint_address: &Pubkey,
    wrapped_mint_authority_address: &Pubkey,
) -> Instruction {
    let accounts = vec![
        AccountMeta::new(*escrow_address, false),
        AccountMeta::new(*destination_address, false),
        AccountMeta::new_readonly(*unwrapped_mint_address, false),
        AccountMeta::new_readonly(*wrapped_mint_address, false),
        AccountMeta::new_readonly(*wrapped_mint_authority_address, false),
        AccountMeta::new_readonly(spl_token_2022::id(), false),
    ];
    let data = TokenWrapInstruction::CloseStuckEscrow.pack();
    Instruction::new_with_bytes(*program_id, &data, accounts)
}

/// Creates `SyncMetadataToToken2022` instruction.
pub fn sync_metadata_to_token_2022(
    program_id: &Pubkey,
    wrapped_mint: &Pubkey,
    wrapped_mint_authority: &Pubkey,
    unwrapped_mint: &Pubkey,
    source_metadata: Option<&Pubkey>,
    owner_program: Option<&Pubkey>,
) -> Instruction {
    let mut accounts = vec![
        AccountMeta::new(*wrapped_mint, false),
        AccountMeta::new_readonly(*wrapped_mint_authority, false),
        AccountMeta::new_readonly(*unwrapped_mint, false),
        AccountMeta::new_readonly(spl_token_2022::id(), false),
    ];

    if let Some(pubkey) = source_metadata {
        accounts.push(AccountMeta::new_readonly(*pubkey, false));
    }

    if let Some(owner) = owner_program {
        accounts.push(AccountMeta::new_readonly(*owner, false));
    }

    let data = TokenWrapInstruction::SyncMetadataToToken2022.pack();
    Instruction::new_with_bytes(*program_id, &data, accounts)
}

/// Creates `SyncMetadataToSplToken` instruction.
pub fn sync_metadata_to_spl_token(
    program_id: &Pubkey,
    metaplex_metadata: &Pubkey,
    wrapped_mint_authority: &Pubkey,
    wrapped_mint: &Pubkey,
    unwrapped_mint: &Pubkey,
    source_metadata: Option<&Pubkey>,
    owner_program: Option<&Pubkey>,
) -> Instruction {
    let mut accounts = vec![
        AccountMeta::new(*metaplex_metadata, false),
        AccountMeta::new(*wrapped_mint_authority, false),
        AccountMeta::new_readonly(*wrapped_mint, false),
        AccountMeta::new_readonly(*unwrapped_mint, false),
        AccountMeta::new_readonly(mpl_token_metadata::ID, false),
        AccountMeta::new_readonly(solana_system_interface::program::id(), false),
        AccountMeta::new_readonly(solana_sysvar::rent::id(), false),
    ];

    if let Some(pubkey) = source_metadata {
        accounts.push(AccountMeta::new_readonly(*pubkey, false));
    }

    if let Some(owner) = owner_program {
        accounts.push(AccountMeta::new_readonly(*owner, false));
    }

    let data = TokenWrapInstruction::SyncMetadataToSplToken.pack();
    Instruction::new_with_bytes(*program_id, &data, accounts)
}