intent_transfer/intrachain/processor/
send_native.rs1use 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 #[account(address = instructions::ID)]
35 pub sysvar_instructions: UncheckedAccount<'info>,
36
37 #[account(seeds = [INTENT_TRANSFER_SEED], bump)]
39 pub intent_transfer_setter: UncheckedAccount<'info>,
40
41 #[account(mut)]
43 pub source: UncheckedAccount<'info>,
44
45 #[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 pub fee_source: UncheckedAccount<'info>,
63
64 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}