intent_transfer/intrachain/processor/
send_tokens.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, verify_symbol_or_mint},
8 INTENT_TRANSFER_SEED,
9};
10use anchor_lang::{prelude::*, solana_program::sysvar::instructions};
11use anchor_spl::{
12 associated_token::AssociatedToken,
13 token::{
14 spl_token::try_ui_amount_into_amount, transfer_checked, Mint, Token, TokenAccount,
15 TransferChecked,
16 },
17};
18use chain_id::ChainId;
19use solana_intents::Intent;
20
21#[derive(Accounts)]
22pub struct SendTokens<'info> {
23 #[account(seeds = [chain_id::SEED], seeds::program = chain_id::ID, bump)]
24 pub chain_id: Account<'info, ChainId>,
25
26 #[account(address = instructions::ID)]
28 pub sysvar_instructions: UncheckedAccount<'info>,
29
30 #[account(seeds = [INTENT_TRANSFER_SEED], bump)]
32 pub intent_transfer_setter: UncheckedAccount<'info>,
33
34 #[account(mut, token::mint = mint)]
35 pub source: Account<'info, TokenAccount>,
36
37 #[account(init_if_needed, payer = sponsor, associated_token::mint = mint, associated_token::authority = destination_owner)]
38 pub destination: Account<'info, TokenAccount>,
39
40 pub mint: Account<'info, Mint>,
41
42 pub metadata: Option<UncheckedAccount<'info>>,
43
44 #[account(
45 init_if_needed,
46 payer = sponsor,
47 space = Nonce::DISCRIMINATOR.len() + Nonce::INIT_SPACE,
48 seeds = [nonce::INTENT_TRANSFER_NONCE_SEED, source.owner.key().as_ref()],
49 bump
50 )]
51 pub nonce: Account<'info, Nonce>,
52
53 #[account(mut)]
54 pub sponsor: Signer<'info>,
55
56 pub destination_owner: AccountInfo<'info>,
58
59 #[account(mut, token::mint = fee_mint, token::authority = source.owner )]
60 pub fee_source: Account<'info, TokenAccount>,
61
62 #[account(init_if_needed, payer = sponsor, associated_token::mint = fee_mint, associated_token::authority = sponsor)]
63 pub fee_destination: Account<'info, TokenAccount>,
64
65 pub fee_mint: Account<'info, Mint>,
66
67 pub fee_metadata: Option<UncheckedAccount<'info>>,
68
69 #[account(seeds = [FEE_CONFIG_SEED, fee_mint.key().as_ref()], bump)]
70 pub fee_config: Account<'info, FeeConfig>,
71
72 pub system_program: Program<'info, System>,
73 pub token_program: Program<'info, Token>,
74 pub associated_token_program: Program<'info, AssociatedToken>,
75}
76
77impl<'info> PaidInstruction<'info> for SendTokens<'info> {
78 fn fee_amount(&self) -> u64 {
79 self.fee_config.intrachain_transfer_fee
80 }
81
82 fn verify_and_collect_accounts<'a>(&'a self) -> VerifyAndCollectAccounts<'a, 'info> {
83 let Self {
84 fee_source,
85 fee_destination,
86 fee_mint,
87 fee_metadata,
88 intent_transfer_setter,
89 token_program,
90 ..
91 } = self;
92 VerifyAndCollectAccounts {
93 fee_source,
94 fee_destination,
95 fee_mint,
96 fee_metadata,
97 intent_transfer_setter,
98 token_program,
99 }
100 }
101}
102
103impl<'info> SendTokens<'info> {
104 pub fn verify_and_send(&mut self, signer_seeds: &[&[&[u8]]]) -> Result<()> {
105 let Self {
106 chain_id,
107 destination,
108 intent_transfer_setter,
109 metadata,
110 mint,
111 source,
112 sysvar_instructions,
113 token_program,
114 nonce,
115 destination_owner,
116 ..
117 } = self;
118
119 let Intent {
120 message:
121 Message {
122 amount,
123 chain_id: expected_chain_id,
124 recipient,
125 symbol_or_mint,
126 nonce: new_nonce,
127 version: _,
128 fee_amount,
129 fee_symbol_or_mint,
130 },
131 signer,
132 } = Intent::load(sysvar_instructions.as_ref())
133 .map_err(Into::<IntentTransferError>::into)?;
134
135 if chain_id.chain_id != expected_chain_id {
136 return err!(IntentTransferError::ChainIdMismatch);
137 }
138
139 verify_symbol_or_mint(&symbol_or_mint, metadata, mint)?;
140 verify_signer_matches_source(signer, source.owner)?;
141
142 require_keys_eq!(
143 recipient,
144 destination_owner.key(),
145 IntentTransferError::RecipientMismatch
146 );
147
148 verify_and_update_nonce(nonce, new_nonce)?;
149
150 transfer_checked(
151 CpiContext::new_with_signer(
152 token_program.to_account_info(),
153 TransferChecked {
154 authority: intent_transfer_setter.to_account_info(),
155 from: source.to_account_info(),
156 mint: mint.to_account_info(),
157 to: destination.to_account_info(),
158 },
159 signer_seeds,
160 ),
161 try_ui_amount_into_amount(amount, mint.decimals)?,
162 mint.decimals,
163 )?;
164
165 self.verify_and_collect_fee(fee_amount, fee_symbol_or_mint, signer_seeds)
166 }
167}