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