Skip to main content

intent_transfer/intrachain/processor/
send_native.rs

1use crate::{
2    config::state::fee_config::{FeeConfig, FEE_CONFIG_SEED},
3    error::IntentTransferError,
4    intrachain::message::Message,
5    nonce::{self, Nonce},
6    verify::{verify_and_update_nonce, verify_signer_matches_source},
7    INTENT_TRANSFER_SEED,
8};
9use anchor_lang::{
10    prelude::*,
11    solana_program::{
12        instruction::{AccountMeta, Instruction},
13        program,
14        sysvar::instructions,
15    },
16    system_program,
17};
18use anchor_spl::{
19    associated_token::AssociatedToken,
20    token::{spl_token::try_ui_amount_into_amount, Mint, Token},
21};
22use chain_id::ChainId;
23use solana_intents::{Intent, SymbolOrMint};
24
25const FOGO_DECIMALS: u8 = 9;
26const SYSTEM_PROGRAM_INTENT_TRANSFER_DISCRIMINATOR: u32 = 4_000_001;
27
28#[derive(Accounts)]
29pub struct SendNative<'info> {
30    #[account(seeds = [chain_id::SEED], seeds::program = chain_id::ID, bump)]
31    pub chain_id: Account<'info, ChainId>,
32
33    /// CHECK: we check the address of this account
34    #[account(address = instructions::ID)]
35    pub sysvar_instructions: UncheckedAccount<'info>,
36
37    /// CHECK: this is just a signer for token program CPIs
38    #[account(seeds = [INTENT_TRANSFER_SEED], bump)]
39    pub intent_transfer_setter: UncheckedAccount<'info>,
40
41    /// CHECK: this is checked against the intent message
42    #[account(mut)]
43    pub source: UncheckedAccount<'info>,
44
45    /// CHECK: this is checked against the intent message
46    #[account(mut)]
47    pub destination: UncheckedAccount<'info>,
48
49    #[account(
50        init_if_needed,
51        payer = sponsor,
52        space = Nonce::DISCRIMINATOR.len() + Nonce::INIT_SPACE,
53        seeds = [nonce::INTENT_TRANSFER_NONCE_SEED, source.key().as_ref()],
54        bump
55    )]
56    pub nonce: Account<'info, Nonce>,
57
58    #[account(mut)]
59    pub sponsor: Signer<'info>,
60
61    /// CHECK: unused
62    pub fee_source: UncheckedAccount<'info>,
63
64    /// CHECK: unused
65    pub fee_destination: UncheckedAccount<'info>,
66
67    pub fee_mint: Account<'info, Mint>,
68
69    pub fee_metadata: Option<UncheckedAccount<'info>>,
70
71    #[account(seeds = [FEE_CONFIG_SEED, fee_mint.key().as_ref()], bump)]
72    pub fee_config: Account<'info, FeeConfig>,
73
74    pub system_program: Program<'info, System>,
75    pub token_program: Program<'info, Token>,
76    pub associated_token_program: Program<'info, AssociatedToken>,
77}
78
79impl<'info> SendNative<'info> {
80    pub fn verify_and_send(&mut self, signer_seeds: &[&[&[u8]]]) -> Result<()> {
81        let Self {
82            chain_id,
83            destination,
84            intent_transfer_setter,
85            sysvar_instructions,
86            nonce,
87            source,
88            ..
89        } = self;
90
91        let Intent {
92            message:
93                Message {
94                    amount,
95                    chain_id: expected_chain_id,
96                    recipient,
97                    symbol_or_mint,
98                    nonce: new_nonce,
99                    version: _,
100                    fee_amount: _,
101                    fee_symbol_or_mint: _,
102                },
103            signer,
104        } = Intent::load(sysvar_instructions.as_ref())
105            .map_err(Into::<IntentTransferError>::into)?;
106
107        if chain_id.chain_id != expected_chain_id {
108            return err!(IntentTransferError::ChainIdMismatch);
109        }
110
111        if symbol_or_mint != SymbolOrMint::Symbol(String::from("FOGO")) {
112            return err!(IntentTransferError::SymbolMismatch);
113        }
114
115        verify_signer_matches_source(signer, source.key())?;
116
117        require_keys_eq!(
118            recipient,
119            destination.key(),
120            IntentTransferError::RecipientMismatch
121        );
122
123        verify_and_update_nonce(nonce, new_nonce)?;
124
125        program::invoke_signed(
126            &Instruction {
127                program_id: system_program::ID,
128                accounts: vec![
129                    AccountMeta::new(source.key(), false),
130                    AccountMeta::new(destination.key(), false),
131                    AccountMeta::new_readonly(intent_transfer_setter.key(), true),
132                ],
133                data: SYSTEM_PROGRAM_INTENT_TRANSFER_DISCRIMINATOR
134                    .to_le_bytes()
135                    .into_iter()
136                    .chain(try_ui_amount_into_amount(amount, FOGO_DECIMALS)?.to_le_bytes())
137                    .collect(),
138            },
139            &[
140                source.to_account_info(),
141                destination.to_account_info(),
142                intent_transfer_setter.to_account_info(),
143            ],
144            signer_seeds,
145        )?;
146        Ok(())
147    }
148}