1#![allow(unexpected_cfgs)] pub mod clock;
4pub mod error;
5pub mod sdk;
6pub mod state;
7pub mod swap;
8pub mod token;
9pub mod utils;
10
11use {
12 crate::{
13 clock::check_deadline,
14 error::ErrorCode,
15 state::*,
16 swap::PostFeeSwapArgs,
17 token::transfer_token_if_needed,
18 utils::*,
19 },
20 anchor_lang::{
21 prelude::*,
22 solana_program::{
23 instruction::{
24 get_stack_height,
25 TRANSACTION_LEVEL_STACK_HEIGHT,
26 },
27 sysvar::instructions as sysvar_instructions,
28 },
29 system_program::System,
30 },
31 anchor_spl::token_interface::{
32 Mint,
33 TokenAccount,
34 TokenInterface,
35 },
36};
37
38declare_id!("PytERJFhAKuNNuaiXkApLfWzwNwSNDACpigT3LwQfou");
39
40#[program]
41pub mod express_relay {
42 use super::*;
43
44 pub fn initialize(ctx: Context<Initialize>, data: InitializeArgs) -> Result<()> {
45 validate_fee_split(data.split_router_default)?;
46 validate_fee_split(data.split_relayer)?;
47
48 let express_relay_metadata_data = &mut ctx.accounts.express_relay_metadata;
49
50 express_relay_metadata_data.admin = *ctx.accounts.admin.key;
51 express_relay_metadata_data.relayer_signer = *ctx.accounts.relayer_signer.key;
52 express_relay_metadata_data.fee_receiver_relayer = *ctx.accounts.fee_receiver_relayer.key;
53 express_relay_metadata_data.split_router_default = data.split_router_default;
54 express_relay_metadata_data.split_relayer = data.split_relayer;
55
56 Ok(())
57 }
58
59 pub fn set_admin(ctx: Context<SetAdmin>) -> Result<()> {
60 let express_relay_metadata_data = &mut ctx.accounts.express_relay_metadata;
61
62 express_relay_metadata_data.admin = *ctx.accounts.admin_new.key;
63
64 Ok(())
65 }
66
67 pub fn set_relayer(ctx: Context<SetRelayer>) -> Result<()> {
68 let express_relay_metadata_data = &mut ctx.accounts.express_relay_metadata;
69
70 express_relay_metadata_data.relayer_signer = *ctx.accounts.relayer_signer.key;
71 express_relay_metadata_data.fee_receiver_relayer = *ctx.accounts.fee_receiver_relayer.key;
72
73 Ok(())
74 }
75
76 pub fn set_secondary_relayer(ctx: Context<SetSecondaryRelayer>) -> Result<()> {
77 let express_relay_metadata_data = &mut ctx.accounts.express_relay_metadata;
78
79 express_relay_metadata_data.secondary_relayer_signer =
80 *ctx.accounts.secondary_relayer_signer.key;
81
82 Ok(())
83 }
84
85 pub fn set_splits(ctx: Context<SetSplits>, data: SetSplitsArgs) -> Result<()> {
86 validate_fee_split(data.split_router_default)?;
87 validate_fee_split(data.split_relayer)?;
88
89 let express_relay_metadata_data = &mut ctx.accounts.express_relay_metadata;
90
91 express_relay_metadata_data.split_router_default = data.split_router_default;
92 express_relay_metadata_data.split_relayer = data.split_relayer;
93
94 Ok(())
95 }
96
97 pub fn set_swap_platform_fee(
98 ctx: Context<SetSplits>,
99 data: SetSwapPlatformFeeArgs,
100 ) -> Result<()> {
101 validate_fee_split(data.swap_platform_fee_bps)?;
102
103 ctx.accounts.express_relay_metadata.swap_platform_fee_bps = data.swap_platform_fee_bps;
104
105 Ok(())
106 }
107
108 pub fn set_router_split(ctx: Context<SetRouterSplit>, data: SetRouterSplitArgs) -> Result<()> {
109 validate_fee_split(data.split_router)?;
110
111 ctx.accounts.config_router.router = *ctx.accounts.router.key;
112 ctx.accounts.config_router.split = data.split_router;
113
114 Ok(())
115 }
116
117 pub fn submit_bid(ctx: Context<SubmitBid>, data: SubmitBidArgs) -> Result<()> {
119 check_deadline(data.deadline)?;
120 ctx.accounts
121 .express_relay_metadata
122 .check_relayer_signer(ctx.accounts.relayer_signer.key)?;
123
124 if get_stack_height() > TRANSACTION_LEVEL_STACK_HEIGHT {
126 return err!(ErrorCode::InvalidCPISubmitBid);
127 }
128
129 let matching_ixs = get_matching_submit_bid_instructions(
133 ctx.accounts.sysvar_instructions.to_account_info(),
134 None,
135 )?;
136 if matching_ixs.len() > 1 {
137 return err!(ErrorCode::MultiplePermissions);
138 }
139
140 handle_bid_payment(ctx, data.bid_amount)
141 }
142
143 pub fn check_permission(ctx: Context<CheckPermission>) -> Result<u64> {
147 let (num_permissions, total_router_fees) = inspect_permissions_in_tx(
148 ctx.accounts.sysvar_instructions.clone(),
149 PermissionInfo {
150 permission: *ctx.accounts.permission.key,
151 router: *ctx.accounts.router.key,
152 config_router: ctx.accounts.config_router.to_account_info(),
153 express_relay_metadata: ctx.accounts.express_relay_metadata.to_account_info(),
154 },
155 )?;
156
157 if num_permissions == 0 {
158 return err!(ErrorCode::MissingPermission);
159 }
160
161 Ok(total_router_fees)
162 }
163
164 pub fn withdraw_fees(ctx: Context<WithdrawFees>) -> Result<()> {
165 let express_relay_metadata = &ctx.accounts.express_relay_metadata;
166 let fee_receiver_admin = &ctx.accounts.fee_receiver_admin;
167
168 let express_relay_metadata_account_info = express_relay_metadata.to_account_info();
169 let rent_express_relay_metadata =
170 Rent::get()?.minimum_balance(express_relay_metadata_account_info.data_len());
171
172 let amount = express_relay_metadata_account_info
173 .lamports()
174 .saturating_sub(rent_express_relay_metadata);
175 if amount == 0 {
176 return Ok(());
177 }
178 transfer_lamports(
179 &express_relay_metadata_account_info,
180 &fee_receiver_admin.to_account_info(),
181 amount,
182 )
183 }
184
185 pub fn swap_internal(ctx: Context<Swap>, data: SwapV2Args) -> Result<()> {
186 ctx.accounts.check_raw_constraints(data.fee_token)?;
187 check_deadline(data.deadline)?;
188 ctx.accounts
189 .express_relay_metadata
190 .check_relayer_signer(ctx.accounts.relayer_signer.key)?;
191
192 let (
193 transfer_swap_fees,
194 PostFeeSwapArgs {
195 amount_searcher_after_fees,
196 amount_user_after_fees,
197 },
198 ) = ctx.accounts.compute_swap_fees(&data)?;
199
200 ctx.accounts.check_enough_balances(&data)?;
206
207 ctx.accounts.transfer_swap_fees_cpi(&transfer_swap_fees)?;
208 transfer_token_if_needed(
210 &ctx.accounts.searcher_ta_mint_searcher,
211 ctx.accounts.user_ata_mint_searcher.to_account_info(),
212 &ctx.accounts.token_program_searcher,
213 &ctx.accounts.searcher,
214 &ctx.accounts.mint_searcher,
215 amount_searcher_after_fees,
216 )?;
217
218 transfer_token_if_needed(
219 &ctx.accounts.user_ata_mint_user,
220 ctx.accounts.searcher_ta_mint_user.to_account_info(),
221 &ctx.accounts.token_program_user,
222 &ctx.accounts.user,
223 &ctx.accounts.mint_user,
224 amount_user_after_fees,
225 )?;
226
227
228 Ok(())
229 }
230
231 pub fn swap(ctx: Context<Swap>, data: SwapArgs) -> Result<()> {
232 let data = ctx.accounts.convert_to_v2(&data);
233 swap_internal(ctx, data)
234 }
235
236 pub fn swap_v2(ctx: Context<Swap>, data: SwapV2Args) -> Result<()> {
237 swap_internal(ctx, data)
238 }
239}
240
241#[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Copy, Debug)]
242pub struct InitializeArgs {
243 pub split_router_default: u64,
244 pub split_relayer: u64,
245}
246
247#[derive(Accounts)]
248pub struct Initialize<'info> {
249 #[account(mut)]
250 pub payer: Signer<'info>,
251
252 #[account(init, payer = payer, space = RESERVE_EXPRESS_RELAY_METADATA, seeds = [SEED_METADATA], bump)]
253 pub express_relay_metadata: Account<'info, ExpressRelayMetadata>,
254
255 pub admin: UncheckedAccount<'info>,
257
258 pub relayer_signer: UncheckedAccount<'info>,
260
261 pub fee_receiver_relayer: UncheckedAccount<'info>,
263
264 pub system_program: Program<'info, System>,
265}
266
267#[derive(Accounts)]
268pub struct SetAdmin<'info> {
269 pub admin: Signer<'info>,
270
271 #[account(mut, seeds = [SEED_METADATA], bump, has_one = admin)]
272 pub express_relay_metadata: Account<'info, ExpressRelayMetadata>,
273
274 pub admin_new: UncheckedAccount<'info>,
276}
277
278#[derive(Accounts)]
279pub struct SetRelayer<'info> {
280 pub admin: Signer<'info>,
281
282 #[account(mut, seeds = [SEED_METADATA], bump, has_one = admin)]
283 pub express_relay_metadata: Account<'info, ExpressRelayMetadata>,
284
285 pub relayer_signer: UncheckedAccount<'info>,
287
288 pub fee_receiver_relayer: UncheckedAccount<'info>,
290}
291
292#[derive(Accounts)]
293pub struct SetSecondaryRelayer<'info> {
294 pub admin: Signer<'info>,
295
296 #[account(mut, seeds = [SEED_METADATA], bump, has_one = admin)]
297 pub express_relay_metadata: Account<'info, ExpressRelayMetadata>,
298
299 pub secondary_relayer_signer: UncheckedAccount<'info>,
301}
302
303#[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Copy, Debug)]
304pub struct SetSplitsArgs {
305 pub split_router_default: u64,
306 pub split_relayer: u64,
307}
308
309#[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Copy, Debug)]
310pub struct SetSwapPlatformFeeArgs {
311 pub swap_platform_fee_bps: u64,
312}
313
314#[derive(Accounts)]
315pub struct SetSplits<'info> {
316 pub admin: Signer<'info>,
317
318 #[account(mut, seeds = [SEED_METADATA], bump, has_one = admin)]
319 pub express_relay_metadata: Account<'info, ExpressRelayMetadata>,
320}
321
322#[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Copy, Debug)]
323pub struct SetRouterSplitArgs {
324 pub split_router: u64,
325}
326
327#[derive(Accounts)]
328pub struct SetRouterSplit<'info> {
329 #[account(mut)]
330 pub admin: Signer<'info>,
331
332 #[account(init_if_needed, payer = admin, space = RESERVE_EXPRESS_RELAY_CONFIG_ROUTER, seeds = [SEED_CONFIG_ROUTER, router.key().as_ref()], bump)]
333 pub config_router: Account<'info, ConfigRouter>,
334
335 #[account(seeds = [SEED_METADATA], bump, has_one = admin)]
336 pub express_relay_metadata: Account<'info, ExpressRelayMetadata>,
337
338 pub router: UncheckedAccount<'info>,
340
341 pub system_program: Program<'info, System>,
342}
343
344#[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Copy, Debug)]
345pub struct SubmitBidArgs {
346 pub deadline: i64,
348 pub bid_amount: u64,
349}
350
351#[derive(Accounts)]
352pub struct SubmitBid<'info> {
353 #[account(mut)]
354 pub searcher: Signer<'info>,
355
356 pub relayer_signer: Signer<'info>,
357
358 pub permission: UncheckedAccount<'info>,
360
361 #[account(mut)]
363 pub router: UncheckedAccount<'info>,
364
365 #[account(seeds = [SEED_CONFIG_ROUTER, router.key().as_ref()], bump)]
367 pub config_router: UncheckedAccount<'info>,
368
369 #[account(mut, seeds = [SEED_METADATA], bump, has_one = fee_receiver_relayer)]
370 pub express_relay_metadata: Account<'info, ExpressRelayMetadata>,
371
372 #[account(mut)]
374 pub fee_receiver_relayer: UncheckedAccount<'info>,
375
376 pub system_program: Program<'info, System>,
377
378 #[account(address = sysvar_instructions::ID)]
380 pub sysvar_instructions: UncheckedAccount<'info>,
381}
382
383#[derive(Accounts)]
384pub struct CheckPermission<'info> {
385 #[account(address = sysvar_instructions::ID)]
387 pub sysvar_instructions: UncheckedAccount<'info>,
388
389 pub permission: UncheckedAccount<'info>,
391
392 pub router: UncheckedAccount<'info>,
394
395 #[account(seeds = [SEED_CONFIG_ROUTER, router.key().as_ref()], bump)]
397 pub config_router: UncheckedAccount<'info>,
398
399 #[account(seeds = [SEED_METADATA], bump)]
400 pub express_relay_metadata: Account<'info, ExpressRelayMetadata>,
401}
402
403#[derive(Accounts)]
404pub struct WithdrawFees<'info> {
405 pub admin: Signer<'info>,
406
407 #[account(mut)]
409 pub fee_receiver_admin: UncheckedAccount<'info>,
410
411 #[account(mut, seeds = [SEED_METADATA], bump, has_one = admin)]
412 pub express_relay_metadata: Account<'info, ExpressRelayMetadata>,
413}
414
415#[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Copy, Debug)]
416pub enum FeeToken {
417 Searcher,
418 User,
419}
420
421#[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Copy, Debug)]
428pub struct SwapArgs {
429 pub deadline: i64,
431 pub amount_searcher: u64,
432 pub amount_user: u64,
433 pub referral_fee_bps: u16,
435 pub fee_token: FeeToken,
437}
438
439impl SwapArgs {
440 pub fn convert_to_v2(&self, swap_platform_fee_bps: u64) -> SwapV2Args {
441 SwapV2Args {
442 deadline: self.deadline,
443 amount_searcher: self.amount_searcher,
444 amount_user: self.amount_user,
445 fee_token: self.fee_token,
446 referral_fee_ppm: u64::from(self.referral_fee_bps) * FEE_BPS_TO_PPM,
447 swap_platform_fee_ppm: swap_platform_fee_bps * FEE_BPS_TO_PPM,
448 }
449 }
450}
451
452#[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Copy, Debug)]
453pub struct SwapV2Args {
454 pub deadline: i64,
456 pub amount_searcher: u64,
457 pub amount_user: u64,
458 pub referral_fee_ppm: u64,
460 pub fee_token: FeeToken,
462 pub swap_platform_fee_ppm: u64,
464}
465
466#[derive(Accounts)]
467pub struct Swap<'info> {
468 pub searcher: Signer<'info>,
470
471 pub user: Signer<'info>,
473
474 #[account(
476 mut,
477 token::mint = mint_searcher,
478 token::authority = searcher,
479 token::token_program = token_program_searcher
480 )]
481 pub searcher_ta_mint_searcher: Box<InterfaceAccount<'info, TokenAccount>>,
482
483 #[account(
484 mut,
485 token::mint = mint_user,
486 token::authority = searcher,
487 token::token_program = token_program_user
488 )]
489 pub searcher_ta_mint_user: Box<InterfaceAccount<'info, TokenAccount>>,
490
491 #[account(
493 mut,
494 associated_token::mint = mint_searcher,
495 associated_token::authority = user,
496 associated_token::token_program = token_program_searcher
497 )]
498 pub user_ata_mint_searcher: Box<InterfaceAccount<'info, TokenAccount>>,
499
500 #[account(
501 mut,
502 associated_token::mint = mint_user,
503 associated_token::authority = user,
504 associated_token::token_program = token_program_user
505 )]
506 pub user_ata_mint_user: Box<InterfaceAccount<'info, TokenAccount>>,
507
508 #[account(mut)]
512 pub router_fee_receiver_ta: UncheckedAccount<'info>,
513
514 #[account(mut)]
516 pub relayer_fee_receiver_ata: UncheckedAccount<'info>,
517
518 #[account(mut)]
520 pub express_relay_fee_receiver_ata: UncheckedAccount<'info>,
521
522 #[account(mint::token_program = token_program_searcher)]
524 pub mint_searcher: Box<InterfaceAccount<'info, Mint>>,
525
526 #[account(mint::token_program = token_program_user)]
527 pub mint_user: Box<InterfaceAccount<'info, Mint>>,
528
529 #[account(
530 mint::token_program = token_program_fee,
531 )]
532 pub mint_fee: Box<InterfaceAccount<'info, Mint>>,
534
535 pub token_program_searcher: Interface<'info, TokenInterface>,
537 pub token_program_user: Interface<'info, TokenInterface>,
538
539 pub token_program_fee: Interface<'info, TokenInterface>,
541
542 #[account(seeds = [SEED_METADATA], bump)]
544 pub express_relay_metadata: Box<Account<'info, ExpressRelayMetadata>>,
545
546 pub relayer_signer: Signer<'info>,
547}