|
@@ -0,0 +1,309 @@
|
|
|
+//! An example program where users can wrap tokens
|
|
|
+//! Source tokens are deposited into a vault in exchange for wrapped tokens at a 1:1 ratio
|
|
|
+//! This is a stateless implementation which relies on PDAs for security
|
|
|
+//!
|
|
|
+//! Initializer initializes a new wrapper:
|
|
|
+//! - SPL token/token-2022 mint (X) the deposit tokens the wrapper will receive
|
|
|
+//! - SPL token/token-2022 mint (Y) the wrapped tokens returned for each deposit token
|
|
|
+//!
|
|
|
+//! Once this wrapper is initialised:
|
|
|
+//! 1. Users can call the wrap function to deposit X and mint Y wrapped tokens
|
|
|
+//! 2. Users can call the unwrap function to burn Y and withdraw X unwrapped tokens
|
|
|
+
|
|
|
+use anchor_lang::prelude::*;
|
|
|
+use anchor_spl::token_interface::{
|
|
|
+ self, Mint, TokenAccount, TokenInterface,
|
|
|
+};
|
|
|
+
|
|
|
+declare_id!("4ZPcGU8MX8oL2u1EtErHzixAbgNBNeE9yoYq3kKMqnAy");
|
|
|
+
|
|
|
+#[program]
|
|
|
+pub mod token_wrapper {
|
|
|
+ use super::*;
|
|
|
+
|
|
|
+ pub const WRAPPER_AUTH_SEED: &[u8] = b"wrapr";
|
|
|
+ pub const WRAPPER_VAULT_SEED: &[u8] = b"vault";
|
|
|
+
|
|
|
+ pub fn initialize(
|
|
|
+ ctx: Context<Initialize>,
|
|
|
+ initializer_amount: u64,
|
|
|
+ ) -> Result<()> {
|
|
|
+ // deposit into vault
|
|
|
+ token_interface::transfer_checked(
|
|
|
+ CpiContext::new(
|
|
|
+ ctx.accounts.deposit_token_program.to_account_info(),
|
|
|
+ token_interface::TransferChecked {
|
|
|
+ from: ctx.accounts.initializer_deposit_token_account.to_account_info(),
|
|
|
+ mint: ctx.accounts.deposit_mint.to_account_info(),
|
|
|
+ to: ctx.accounts.deposit_token_vault.to_account_info(),
|
|
|
+ authority: ctx.accounts.initializer.to_account_info(),
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ initializer_amount,
|
|
|
+ ctx.accounts.deposit_mint.decimals,
|
|
|
+ )?;
|
|
|
+
|
|
|
+ // mint wrapped tokens
|
|
|
+ let inner_seeds = [
|
|
|
+ WRAPPER_AUTH_SEED,
|
|
|
+ ctx.accounts.deposit_mint.to_account_info().key.as_ref(),
|
|
|
+ ctx.accounts.wrapped_mint.to_account_info().key.as_ref(),
|
|
|
+ &[*ctx.bumps.get("wrapper_authority").unwrap()],
|
|
|
+ ];
|
|
|
+ let signer_seeds = &[&inner_seeds[..]];
|
|
|
+ token_interface::mint_to(
|
|
|
+ CpiContext::new_with_signer(
|
|
|
+ ctx.accounts.wrapped_token_program.to_account_info(),
|
|
|
+ token_interface::MintTo {
|
|
|
+ mint: ctx.accounts.wrapped_mint.to_account_info(),
|
|
|
+ to: ctx.accounts.initializer_wrapped_token_account.to_account_info(),
|
|
|
+ authority: ctx.accounts.wrapper_authority.to_account_info(),
|
|
|
+ },
|
|
|
+ signer_seeds,
|
|
|
+ ),
|
|
|
+ initializer_amount,
|
|
|
+ )?;
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn wrap(ctx: Context<Wrap>, wrap_amount: u64) -> Result<()> {
|
|
|
+ // deposit into vault
|
|
|
+ token_interface::transfer_checked(
|
|
|
+ CpiContext::new(
|
|
|
+ ctx.accounts.deposit_token_program.to_account_info(),
|
|
|
+ token_interface::TransferChecked {
|
|
|
+ from: ctx.accounts.user_deposit_token_account.to_account_info(),
|
|
|
+ mint: ctx.accounts.deposit_mint.to_account_info(),
|
|
|
+ to: ctx.accounts.deposit_token_vault.to_account_info(),
|
|
|
+ authority: ctx.accounts.signer.to_account_info(),
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ wrap_amount,
|
|
|
+ ctx.accounts.deposit_mint.decimals,
|
|
|
+ )?;
|
|
|
+
|
|
|
+ // mint wrapped tokens
|
|
|
+ let inner_seeds = [
|
|
|
+ WRAPPER_AUTH_SEED,
|
|
|
+ ctx.accounts.deposit_mint.to_account_info().key.as_ref(),
|
|
|
+ ctx.accounts.wrapped_mint.to_account_info().key.as_ref(),
|
|
|
+ &[*ctx.bumps.get("wrapper_authority").unwrap()],
|
|
|
+ ];
|
|
|
+ let signer_seeds = &[&inner_seeds[..]];
|
|
|
+ token_interface::mint_to(
|
|
|
+ CpiContext::new_with_signer(
|
|
|
+ ctx.accounts.wrapped_token_program.to_account_info(),
|
|
|
+ token_interface::MintTo {
|
|
|
+ mint: ctx.accounts.wrapped_mint.to_account_info(),
|
|
|
+ to: ctx.accounts.user_wrapped_token_account.to_account_info(),
|
|
|
+ authority: ctx.accounts.wrapper_authority.to_account_info(),
|
|
|
+ },
|
|
|
+ signer_seeds,
|
|
|
+ ),
|
|
|
+ wrap_amount,
|
|
|
+ )?;
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn unwrap(ctx: Context<Unwrap>, unwrap_amount: u64) -> Result<()> {
|
|
|
+ // burn wrapped tokens
|
|
|
+ token_interface::burn(
|
|
|
+ CpiContext::new(
|
|
|
+ ctx.accounts.wrapped_token_program.to_account_info(),
|
|
|
+ token_interface::Burn {
|
|
|
+ mint: ctx.accounts.wrapped_mint.to_account_info(),
|
|
|
+ from: ctx.accounts.user_wrapped_token_account.to_account_info(),
|
|
|
+ authority: ctx.accounts.signer.to_account_info(),
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ unwrap_amount,
|
|
|
+ )?;
|
|
|
+
|
|
|
+ // withdraw from vault
|
|
|
+ let inner_seeds = [
|
|
|
+ WRAPPER_AUTH_SEED,
|
|
|
+ ctx.accounts.deposit_mint.to_account_info().key.as_ref(),
|
|
|
+ ctx.accounts.wrapped_mint.to_account_info().key.as_ref(),
|
|
|
+ &[*ctx.bumps.get("wrapper_authority").unwrap()],
|
|
|
+ ];
|
|
|
+ let signer_seeds = &[&inner_seeds[..]];
|
|
|
+ token_interface::transfer_checked(
|
|
|
+ CpiContext::new_with_signer(
|
|
|
+ ctx.accounts.deposit_token_program.to_account_info(),
|
|
|
+ token_interface::TransferChecked {
|
|
|
+ from: ctx.accounts.deposit_token_vault.to_account_info(),
|
|
|
+ mint: ctx.accounts.deposit_mint.to_account_info(),
|
|
|
+ to: ctx.accounts.user_deposit_token_account.to_account_info(),
|
|
|
+ authority: ctx.accounts.wrapper_authority.to_account_info(),
|
|
|
+ },
|
|
|
+ signer_seeds
|
|
|
+ ),
|
|
|
+ unwrap_amount,
|
|
|
+ ctx.accounts.deposit_mint.decimals,
|
|
|
+ )?;
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Accounts)]
|
|
|
+#[instruction(initializer_amount: u64)]
|
|
|
+pub struct Initialize<'info> {
|
|
|
+ #[account(mut)]
|
|
|
+ pub initializer: Signer<'info>,
|
|
|
+
|
|
|
+ #[account(
|
|
|
+ mint::token_program = deposit_token_program,
|
|
|
+ )]
|
|
|
+ pub deposit_mint: Box<InterfaceAccount<'info, Mint>>,
|
|
|
+
|
|
|
+ #[account(init,
|
|
|
+ payer = initializer,
|
|
|
+ mint::decimals = deposit_mint.decimals,
|
|
|
+ mint::authority = wrapper_authority,
|
|
|
+ mint::token_program = wrapped_token_program,
|
|
|
+ )]
|
|
|
+ pub wrapped_mint: Box<InterfaceAccount<'info, Mint>>,
|
|
|
+
|
|
|
+ /// Program-owned vault to store deposited tokens
|
|
|
+ #[account(init,
|
|
|
+ seeds = [WRAPPER_VAULT_SEED, deposit_mint.key().as_ref(), wrapped_mint.key().as_ref()],
|
|
|
+ bump,
|
|
|
+ payer = initializer,
|
|
|
+ token::mint = deposit_mint,
|
|
|
+ token::authority = wrapper_authority,
|
|
|
+ token::token_program = deposit_token_program,
|
|
|
+ )]
|
|
|
+ pub deposit_token_vault: Box<InterfaceAccount<'info, TokenAccount>>,
|
|
|
+
|
|
|
+ /// User token account to deposit tokens from
|
|
|
+ #[account(mut,
|
|
|
+ constraint = initializer_deposit_token_account.amount >= initializer_amount,
|
|
|
+ token::mint = deposit_mint,
|
|
|
+ token::authority = initializer,
|
|
|
+ token::token_program = deposit_token_program,
|
|
|
+ )]
|
|
|
+ pub initializer_deposit_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
|
|
+
|
|
|
+ /// User token account to send wrapped tokens to
|
|
|
+ #[account(init,
|
|
|
+ payer = initializer,
|
|
|
+ token::mint = wrapped_mint,
|
|
|
+ token::authority = initializer,
|
|
|
+ token::token_program = wrapped_token_program,
|
|
|
+ )]
|
|
|
+ pub initializer_wrapped_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
|
|
+
|
|
|
+ /// CHECK: PDA owned by the program
|
|
|
+ #[account(mut,
|
|
|
+ seeds = [WRAPPER_AUTH_SEED, deposit_mint.key().as_ref(), wrapped_mint.key().as_ref()],
|
|
|
+ bump,
|
|
|
+ )]
|
|
|
+ pub wrapper_authority: AccountInfo<'info>,
|
|
|
+
|
|
|
+ pub system_program: Program<'info, System>,
|
|
|
+ pub deposit_token_program: Interface<'info, TokenInterface>,
|
|
|
+ pub wrapped_token_program: Interface<'info, TokenInterface>,
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Accounts)]
|
|
|
+#[instruction(wrap_amount: u64)]
|
|
|
+pub struct Wrap<'info> {
|
|
|
+ #[account(mut)]
|
|
|
+ pub signer: Signer<'info>,
|
|
|
+
|
|
|
+ #[account(
|
|
|
+ mint::token_program = deposit_token_program,
|
|
|
+ )]
|
|
|
+ pub deposit_mint: Box<InterfaceAccount<'info, Mint>>,
|
|
|
+
|
|
|
+ #[account(mut,
|
|
|
+ mint::authority = wrapper_authority,
|
|
|
+ mint::token_program = wrapped_token_program,
|
|
|
+ )]
|
|
|
+ pub wrapped_mint: Box<InterfaceAccount<'info, Mint>>,
|
|
|
+
|
|
|
+ #[account(mut,
|
|
|
+ seeds = [WRAPPER_VAULT_SEED, deposit_mint.key().as_ref(), wrapped_mint.key().as_ref()],
|
|
|
+ bump,
|
|
|
+ token::mint = deposit_mint,
|
|
|
+ token::authority = wrapper_authority,
|
|
|
+ token::token_program = deposit_token_program,
|
|
|
+ )]
|
|
|
+ pub deposit_token_vault: Box<InterfaceAccount<'info, TokenAccount>>,
|
|
|
+
|
|
|
+ #[account(mut,
|
|
|
+ constraint = user_deposit_token_account.amount >= wrap_amount,
|
|
|
+ token::mint = deposit_mint,
|
|
|
+ token::token_program = deposit_token_program,
|
|
|
+ )]
|
|
|
+ pub user_deposit_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
|
|
+
|
|
|
+ #[account(mut,
|
|
|
+ token::mint = wrapped_mint,
|
|
|
+ token::token_program = wrapped_token_program,
|
|
|
+ )]
|
|
|
+ pub user_wrapped_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
|
|
+
|
|
|
+ /// CHECK: PDA owned by the program
|
|
|
+ #[account(mut,
|
|
|
+ seeds = [WRAPPER_AUTH_SEED, deposit_mint.key().as_ref(), wrapped_mint.key().as_ref()],
|
|
|
+ bump,
|
|
|
+ )]
|
|
|
+ pub wrapper_authority: AccountInfo<'info>,
|
|
|
+
|
|
|
+ pub deposit_token_program: Interface<'info, TokenInterface>,
|
|
|
+ pub wrapped_token_program: Interface<'info, TokenInterface>,
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Accounts)]
|
|
|
+#[instruction(unwrap_amount: u64)]
|
|
|
+pub struct Unwrap<'info> {
|
|
|
+ #[account(mut)]
|
|
|
+ pub signer: Signer<'info>,
|
|
|
+
|
|
|
+ #[account(
|
|
|
+ mint::token_program = deposit_token_program,
|
|
|
+ )]
|
|
|
+ pub deposit_mint: Box<InterfaceAccount<'info, Mint>>,
|
|
|
+
|
|
|
+ #[account(mut,
|
|
|
+ mint::token_program = wrapped_token_program,
|
|
|
+ mint::authority = wrapper_authority,
|
|
|
+ )]
|
|
|
+ pub wrapped_mint: Box<InterfaceAccount<'info, Mint>>,
|
|
|
+
|
|
|
+ #[account(mut,
|
|
|
+ seeds = [WRAPPER_VAULT_SEED, deposit_mint.key().as_ref(), wrapped_mint.key().as_ref()],
|
|
|
+ bump,
|
|
|
+ token::mint = deposit_mint,
|
|
|
+ token::authority = wrapper_authority,
|
|
|
+ token::token_program = deposit_token_program,
|
|
|
+ )]
|
|
|
+ pub deposit_token_vault: Box<InterfaceAccount<'info, TokenAccount>>,
|
|
|
+
|
|
|
+ #[account(mut,
|
|
|
+ token::mint = deposit_mint,
|
|
|
+ token::token_program = deposit_token_program,
|
|
|
+ )]
|
|
|
+ pub user_deposit_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
|
|
+
|
|
|
+ #[account(mut,
|
|
|
+ constraint = user_wrapped_token_account.amount >= unwrap_amount,
|
|
|
+ token::mint = wrapped_mint,
|
|
|
+ token::token_program = wrapped_token_program,
|
|
|
+ )]
|
|
|
+ pub user_wrapped_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
|
|
+
|
|
|
+ /// CHECK: PDA owned by the program
|
|
|
+ #[account(mut,
|
|
|
+ seeds = [crate::token_wrapper::WRAPPER_AUTH_SEED, deposit_mint.key().as_ref(), wrapped_mint.key().as_ref()],
|
|
|
+ bump,
|
|
|
+ )]
|
|
|
+ pub wrapper_authority: AccountInfo<'info>,
|
|
|
+
|
|
|
+ pub deposit_token_program: Interface<'info, TokenInterface>,
|
|
|
+ pub wrapped_token_program: Interface<'info, TokenInterface>,
|
|
|
+}
|