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