express_relay/
lib.rs

1#![allow(unexpected_cfgs)] // https://github.com/coral-xyz/anchor/issues/3401
2
3pub 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    /// Submits a bid for a particular (permission, router) pair and distributes bids according to splits.
118    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        // check that not cpi.
125        if get_stack_height() > TRANSACTION_LEVEL_STACK_HEIGHT {
126            return err!(ErrorCode::InvalidCPISubmitBid);
127        }
128
129        // Check "no reentrancy"--SubmitBid instruction only used once in transaction.
130        // This is done to prevent an exploit where a searcher submits a transaction with multiple `SubmitBid`` instructions with different permission keys.
131        // That could allow the searcher to win the right to perform the transaction if they won just one of the auctions.
132        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    /// Checks if permissioning exists for a particular (permission, router) pair within the same transaction.
144    /// Permissioning takes the form of a SubmitBid instruction with matching permission and router accounts.
145    /// Returns the fees paid to the router in the matching instructions.
146    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        // We want the program to never fail in the CPI transfers after `check_enough_balances`.
201        // This guarantees auction server than when a simulated transaction fails with the InsufficientUserFunds error,
202        // the transaction was correct and executable other than the user having insufficient balance.
203        // The checks above this line combined with `check_enough_balances` should guarantee that the CPIs will never fail
204        // and `check_enough_balances` should be the last check.
205        ctx.accounts.check_enough_balances(&data)?;
206
207        ctx.accounts.transfer_swap_fees_cpi(&transfer_swap_fees)?;
208        // Transfer tokens
209        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    /// CHECK: this is just the admin's PK.
256    pub admin: UncheckedAccount<'info>,
257
258    /// CHECK: this is just the relayer's signer PK.
259    pub relayer_signer: UncheckedAccount<'info>,
260
261    /// CHECK: this is just a PK for the relayer to receive fees at.
262    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    /// CHECK: this is just the new admin PK.
275    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    /// CHECK: this is the relayer public key which can be any arbitrary key.
286    pub relayer_signer: UncheckedAccount<'info>,
287
288    /// CHECK: this is the relayer fee receiver public key which can be any arbitrary key.
289    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    /// CHECK: this is the secondary relayer public key which can be any arbitrary key.
300    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    /// CHECK: this is just the router fee receiver PK.
339    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    // deadline as a unix timestamp in seconds
347    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    /// CHECK: this is the permission key. Often the permission key refers to an on-chain account storing the opportunity; other times, it could refer to the 32 byte hash of identifying opportunity data. We include the permission as an account instead of putting it in the instruction data to save transaction size via caching in case of repeated use.
359    pub permission: UncheckedAccount<'info>,
360
361    /// CHECK: don't care what this looks like.
362    #[account(mut)]
363    pub router: UncheckedAccount<'info>,
364
365    /// CHECK: Some routers might have an initialized ConfigRouter at the enforced PDA address which specifies a custom routing fee split. If the ConfigRouter is unitialized we will default to the routing fee split defined in the global ExpressRelayMetadata. We need to pass this account to check whether it exists and therefore there is a custom fee split.
366    #[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    /// CHECK: this is just a PK for the relayer to receive fees at.
373    #[account(mut)]
374    pub fee_receiver_relayer: UncheckedAccount<'info>,
375
376    pub system_program: Program<'info, System>,
377
378    /// CHECK: this is the sysvar instructions account.
379    #[account(address = sysvar_instructions::ID)]
380    pub sysvar_instructions: UncheckedAccount<'info>,
381}
382
383#[derive(Accounts)]
384pub struct CheckPermission<'info> {
385    /// CHECK: this is the sysvar instructions account.
386    #[account(address = sysvar_instructions::ID)]
387    pub sysvar_instructions: UncheckedAccount<'info>,
388
389    /// CHECK: this is the permission key. Often the permission key refers to an on-chain account storing the opportunity; other times, it could refer to the 32 byte hash of identifying opportunity data. We include the permission as an account instead of putting it in the instruction data to save transaction size via caching in case of repeated use.
390    pub permission: UncheckedAccount<'info>,
391
392    /// CHECK: this is the router address.
393    pub router: UncheckedAccount<'info>,
394
395    /// CHECK: this cannot be checked against ConfigRouter bc it may not be initialized.
396    #[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    /// CHECK: this is just the PK where the fees should be sent.
408    #[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/// For all swap instructions and contexts, the mint is defined with respect to the party that provides that token in the swap.
422/// So `mint_searcher` refers to the token that the searcher provides in the swap,
423/// and `mint_user` refers to the token that the user provides in the swap.
424/// The `{X}_ta/ata_mint_{Y}` notation indicates the (associated) token account belonging to X for the mint of the token Y provides in the swap.
425/// For example, `searcher_ta_mint_searcher` is the searcher's token account of the mint the searcher provides in the swap,
426/// and `user_ata_mint_searcher` is the user's token account of the same mint.
427#[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Copy, Debug)]
428pub struct SwapArgs {
429    // deadline as a unix timestamp in seconds
430    pub deadline:         i64,
431    pub amount_searcher:  u64,
432    pub amount_user:      u64,
433    // The referral fee is specified in basis points
434    pub referral_fee_bps: u16,
435    // Token in which the fees will be paid
436    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    /// deadline as a unix timestamp in seconds
455    pub deadline:              i64,
456    pub amount_searcher:       u64,
457    pub amount_user:           u64,
458    /// The referral fee is specified in parts per million
459    pub referral_fee_ppm:      u64,
460    /// Token in which the fees will be paid
461    pub fee_token:             FeeToken,
462    /// The platform fee is specified in parts per million
463    pub swap_platform_fee_ppm: u64,
464}
465
466#[derive(Accounts)]
467pub struct Swap<'info> {
468    /// Searcher is the party that fulfills the quote request
469    pub searcher: Signer<'info>,
470
471    /// User is the party that requests the quote
472    pub user: Signer<'info>,
473
474    // Searcher accounts
475    #[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    // User accounts
492    #[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    // Fee receivers
509    /// Router fee receiver token account: the referrer can provide an arbitrary receiver for the router fee
510    /// CHECK: we check this account manually since it can be uninitialized if the fee is 0
511    #[account(mut)]
512    pub router_fee_receiver_ta: UncheckedAccount<'info>,
513
514    /// CHECK: we check this account manually since it can be uninitialized if the fee is 0
515    #[account(mut)]
516    pub relayer_fee_receiver_ata: UncheckedAccount<'info>,
517
518    /// CHECK: we check this account manually since it can be uninitialized if the fee is 0
519    #[account(mut)]
520    pub express_relay_fee_receiver_ata: UncheckedAccount<'info>,
521
522    // Mints
523    #[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    /// CHECK: we check that this is set correctly based on the fee token manually since the V2 arguments are incompatible on the wire
533    pub mint_fee: Box<InterfaceAccount<'info, Mint>>,
534
535    // Token programs
536    pub token_program_searcher: Interface<'info, TokenInterface>,
537    pub token_program_user:     Interface<'info, TokenInterface>,
538
539    /// CHECK: we check that this is set correctly based on the fee token manually since the V2 arguments are incompatible on the wire
540    pub token_program_fee: Interface<'info, TokenInterface>,
541
542    /// Express relay configuration
543    #[account(seeds = [SEED_METADATA], bump)]
544    pub express_relay_metadata: Box<Account<'info, ExpressRelayMetadata>>,
545
546    pub relayer_signer: Signer<'info>,
547}