jito_priority_fee_distribution/
lib.rs

1use anchor_lang::{prelude::*, solana_program::clock::Clock};
2#[cfg(not(feature = "no-entrypoint"))]
3use solana_security_txt::security_txt;
4
5use crate::{
6    state::{
7        ClaimStatus, Config, MerkleRoot, MerkleRootUploadConfig, PriorityFeeDistributionAccount,
8    },
9    ErrorCode::Unauthorized,
10};
11
12#[cfg(not(feature = "no-entrypoint"))]
13security_txt! {
14    // Required fields
15    name: "Jito Priority Fee Program",
16    project_url: "https://jito.network/",
17    contacts: "email:support@jito.network",
18    policy: "https://github.com/jito-foundation/jito-programs",
19    // Optional Fields
20    preferred_languages: "en",
21    source_code: "https://github.com/jito-foundation/jito-programs",
22    source_revision: std::env!("GIT_SHA"),
23    source_release: std::env!("GIT_REF_NAME")
24}
25
26pub mod merkle_proof;
27pub mod state;
28
29declare_id!("Priority6weCZ5HwDn29NxLFpb7TDp2iLZ6XKc5e8d3");
30
31#[program]
32pub mod jito_priority_fee_distribution {
33    use jito_programs_vote_state::VoteState;
34    use solana_program::native_token::lamports_to_sol;
35
36    use super::*;
37    use crate::ErrorCode::*;
38
39    /// Initialize a singleton instance of the [Config] account.
40    pub fn initialize(
41        ctx: Context<Initialize>,
42        authority: Pubkey,
43        expired_funds_account: Pubkey,
44        num_epochs_valid: u64,
45        max_validator_commission_bps: u16,
46        bump: u8,
47    ) -> Result<()> {
48        let cfg = &mut ctx.accounts.config;
49        cfg.authority = authority;
50        cfg.expired_funds_account = expired_funds_account;
51        cfg.num_epochs_valid = num_epochs_valid;
52        cfg.max_validator_commission_bps = max_validator_commission_bps;
53        cfg.go_live_epoch = u64::MAX;
54        cfg.bump = bump;
55        cfg.validate()?;
56
57        Ok(())
58    }
59
60    /// Initialize a new [PriorityFeeDistributionAccount] associated with the given validator vote key
61    /// and current epoch.
62    pub fn initialize_priority_fee_distribution_account(
63        ctx: Context<InitializePriorityFeeDistributionAccount>,
64        merkle_root_upload_authority: Pubkey,
65        validator_commission_bps: u16,
66        bump: u8,
67    ) -> Result<()> {
68        if validator_commission_bps > ctx.accounts.config.max_validator_commission_bps {
69            return Err(MaxValidatorCommissionFeeBpsExceeded.into());
70        }
71
72        let validator_vote_account_node_pubkey =
73            VoteState::deserialize_node_pubkey(&ctx.accounts.validator_vote_account)?;
74        if validator_vote_account_node_pubkey != *ctx.accounts.signer.key {
75            return Err(Unauthorized.into());
76        }
77
78        let current_epoch = Clock::get()?.epoch;
79
80        let distribution_acc = &mut ctx.accounts.priority_fee_distribution_account;
81        distribution_acc.validator_vote_account = ctx.accounts.validator_vote_account.key();
82        distribution_acc.epoch_created_at = current_epoch;
83        distribution_acc.validator_commission_bps = validator_commission_bps;
84        distribution_acc.merkle_root_upload_authority = merkle_root_upload_authority;
85        distribution_acc.merkle_root = None;
86        distribution_acc.expires_at = current_epoch
87            .checked_add(ctx.accounts.config.num_epochs_valid)
88            .ok_or(ArithmeticError)?;
89        distribution_acc.bump = bump;
90        distribution_acc.validate()?;
91
92        emit!(PriorityFeeDistributionAccountInitializedEvent {
93            priority_fee_distribution_account: distribution_acc.key(),
94        });
95
96        Ok(())
97    }
98
99    /// Update config fields. Only the [Config] authority can invoke this.
100    pub fn update_config(ctx: Context<UpdateConfig>, new_config: Config) -> Result<()> {
101        UpdateConfig::auth(&ctx)?;
102
103        let config = &mut ctx.accounts.config;
104        config.authority = new_config.authority;
105        config.expired_funds_account = new_config.expired_funds_account;
106        config.num_epochs_valid = new_config.num_epochs_valid;
107        config.max_validator_commission_bps = new_config.max_validator_commission_bps;
108        config.go_live_epoch = new_config.go_live_epoch;
109        config.validate()?;
110
111        emit!(ConfigUpdatedEvent {
112            authority: ctx.accounts.authority.key(),
113        });
114
115        Ok(())
116    }
117
118    /// Uploads a merkle root to the provided [PriorityFeeDistributionAccount]. This instruction may be
119    /// invoked many times as long as the account is at least one epoch old and not expired; and
120    /// no funds have already been claimed. Only the `merkle_root_upload_authority` has the
121    /// authority to invoke.
122    pub fn upload_merkle_root(
123        ctx: Context<UploadMerkleRoot>,
124        root: [u8; 32],
125        max_total_claim: u64,
126        max_num_nodes: u64,
127    ) -> Result<()> {
128        UploadMerkleRoot::auth(&ctx)?;
129
130        let current_epoch = Clock::get()?.epoch;
131        let distribution_acc = &mut ctx.accounts.priority_fee_distribution_account;
132
133        if let Some(merkle_root) = &distribution_acc.merkle_root {
134            if merkle_root.num_nodes_claimed > 0 {
135                return Err(Unauthorized.into());
136            }
137        }
138        if current_epoch <= distribution_acc.epoch_created_at {
139            return Err(PrematureMerkleRootUpload.into());
140        }
141
142        if current_epoch > distribution_acc.expires_at {
143            return Err(ExpiredPriorityFeeDistributionAccount.into());
144        }
145
146        distribution_acc.merkle_root = Some(MerkleRoot {
147            root,
148            max_total_claim,
149            max_num_nodes,
150            total_funds_claimed: 0,
151            num_nodes_claimed: 0,
152        });
153        distribution_acc.validate()?;
154
155        emit!(MerkleRootUploadedEvent {
156            merkle_root_upload_authority: ctx.accounts.merkle_root_upload_authority.key(),
157            priority_fee_distribution_account: distribution_acc.key(),
158        });
159
160        Ok(())
161    }
162
163    /// Anyone can invoke this only after the [PriorityFeeDistributionAccount] has expired.
164    /// This instruction will return any rent back to `claimant` and close the account
165    pub fn close_claim_status(ctx: Context<CloseClaimStatus>) -> Result<()> {
166        let claim_status = &ctx.accounts.claim_status;
167
168        // can only claim after claim_status has expired to prevent draining.
169        if Clock::get()?.epoch <= claim_status.expires_at {
170            return Err(PrematureCloseClaimStatus.into());
171        }
172
173        emit!(ClaimStatusClosedEvent {
174            claim_status_payer: ctx.accounts.claim_status_payer.key(),
175            claim_status_account: claim_status.key(),
176        });
177
178        Ok(())
179    }
180
181    /// Anyone can invoke this only after the [PriorityFeeDistributionAccount] has expired.
182    /// This instruction will send any unclaimed funds to the designated `expired_funds_account`
183    /// before closing and returning the rent exempt funds to the validator.
184    pub fn close_priority_fee_distribution_account(
185        ctx: Context<ClosePriorityFeeDistributionAccount>,
186        _epoch: u64,
187    ) -> Result<()> {
188        ClosePriorityFeeDistributionAccount::auth(&ctx)?;
189
190        let priority_fee_distribution_account = &mut ctx.accounts.priority_fee_distribution_account;
191
192        if Clock::get()?.epoch <= priority_fee_distribution_account.expires_at {
193            return Err(PrematureClosePriorityFeeDistributionAccount.into());
194        }
195
196        let expired_amount = PriorityFeeDistributionAccount::claim_expired(
197            priority_fee_distribution_account.to_account_info(),
198            ctx.accounts.expired_funds_account.to_account_info(),
199        )?;
200        priority_fee_distribution_account.validate()?;
201
202        emit!(PriorityFeeDistributionAccountClosedEvent {
203            expired_funds_account: ctx.accounts.expired_funds_account.key(),
204            priority_fee_distribution_account: priority_fee_distribution_account.key(),
205            expired_amount,
206        });
207
208        Ok(())
209    }
210
211    /// Claims tokens from the [PriorityFeeDistributionAccount].
212    pub fn claim(ctx: Context<Claim>, _bump: u8, amount: u64, proof: Vec<[u8; 32]>) -> Result<()> {
213        Claim::auth(&ctx)?;
214
215        let claim_status = &mut ctx.accounts.claim_status;
216
217        let claimant_account = &mut ctx.accounts.claimant;
218        let priority_fee_distribution_account = &mut ctx.accounts.priority_fee_distribution_account;
219
220        let clock = Clock::get()?;
221        if clock.epoch > priority_fee_distribution_account.expires_at {
222            return Err(ExpiredPriorityFeeDistributionAccount.into());
223        }
224
225        let tip_distribution_info = priority_fee_distribution_account.to_account_info();
226        let tip_distribution_epoch_expires_at = priority_fee_distribution_account.expires_at;
227        let merkle_root = priority_fee_distribution_account
228            .merkle_root
229            .as_mut()
230            .ok_or(RootNotUploaded)?;
231
232        // Verify the merkle proof.
233        let node = &solana_program::hash::hashv(&[
234            &[0u8],
235            &solana_program::hash::hashv(&[
236                &claimant_account.key().to_bytes(),
237                &amount.to_le_bytes(),
238            ])
239            .to_bytes(),
240        ]);
241
242        if !merkle_proof::verify(proof, merkle_root.root, node.to_bytes()) {
243            return Err(InvalidProof.into());
244        }
245
246        PriorityFeeDistributionAccount::claim(
247            tip_distribution_info,
248            claimant_account.to_account_info(),
249            amount,
250        )?;
251
252        // Mark it claimed.
253        claim_status.claim_status_payer = ctx.accounts.payer.key();
254        claim_status.expires_at = tip_distribution_epoch_expires_at;
255
256        merkle_root.total_funds_claimed = merkle_root
257            .total_funds_claimed
258            .checked_add(amount)
259            .ok_or(ArithmeticError)?;
260        if merkle_root.total_funds_claimed > merkle_root.max_total_claim {
261            return Err(ExceedsMaxClaim.into());
262        }
263
264        merkle_root.num_nodes_claimed = merkle_root
265            .num_nodes_claimed
266            .checked_add(1)
267            .ok_or(ArithmeticError)?;
268        if merkle_root.num_nodes_claimed > merkle_root.max_num_nodes {
269            return Err(ExceedsMaxNumNodes.into());
270        }
271
272        emit!(ClaimedEvent {
273            priority_fee_distribution_account: priority_fee_distribution_account.key(),
274            payer: ctx.accounts.payer.key(),
275            claimant: claimant_account.key(),
276            amount
277        });
278
279        priority_fee_distribution_account.validate()?;
280
281        Ok(())
282    }
283
284    pub fn initialize_merkle_root_upload_config(
285        ctx: Context<InitializeMerkleRootUploadConfig>,
286        authority: Pubkey,
287        original_authority: Pubkey,
288    ) -> Result<()> {
289        // Call the authorize function
290        InitializeMerkleRootUploadConfig::auth(&ctx)?;
291
292        // Set the bump and override authority
293        let merkle_root_upload_config = &mut ctx.accounts.merkle_root_upload_config;
294        merkle_root_upload_config.override_authority = authority;
295        merkle_root_upload_config.original_upload_authority = original_authority;
296        merkle_root_upload_config.bump = ctx.bumps.merkle_root_upload_config;
297        Ok(())
298    }
299
300    pub fn update_merkle_root_upload_config(
301        ctx: Context<UpdateMerkleRootUploadConfig>,
302        authority: Pubkey,
303        original_authority: Pubkey,
304    ) -> Result<()> {
305        // Call the authorize function
306        UpdateMerkleRootUploadConfig::auth(&ctx)?;
307
308        // Update override authority
309        let merkle_root_upload_config = &mut ctx.accounts.merkle_root_upload_config;
310        merkle_root_upload_config.override_authority = authority;
311        merkle_root_upload_config.original_upload_authority = original_authority;
312
313        Ok(())
314    }
315
316    pub fn migrate_tda_merkle_root_upload_authority(
317        ctx: Context<MigrateTdaMerkleRootUploadAuthority>,
318    ) -> Result<()> {
319        let distribution_account = &mut ctx.accounts.priority_fee_distribution_account;
320        // Validate TDA has no MerkleRoot uploaded to it
321        if distribution_account.merkle_root.is_some() {
322            return Err(InvalidTdaForMigration.into());
323        }
324        // Validate the TDA key is the acceptable original authority (i.e. the original Jito Lab's authority)
325        if distribution_account.merkle_root_upload_authority
326            != ctx
327                .accounts
328                .merkle_root_upload_config
329                .original_upload_authority
330        {
331            return Err(InvalidTdaForMigration.into());
332        }
333
334        // Change the TDA's root upload authority
335        distribution_account.merkle_root_upload_authority =
336            ctx.accounts.merkle_root_upload_config.override_authority;
337
338        Ok(())
339    }
340
341    pub fn transfer_priority_fee_tips(
342        ctx: Context<TransferPriorityFeeTips>,
343        lamports: u64,
344    ) -> Result<()> {
345        let epoch = Clock::get()?.epoch;
346        // Valdiate the PFDA is in the current epoch
347        require!(
348            ctx.accounts
349                .priority_fee_distribution_account
350                .epoch_created_at
351                == epoch,
352            ErrorCode::AccountValidationFailure
353        );
354
355        ctx.accounts
356            .priority_fee_distribution_account
357            .increment_total_lamports_transferred(lamports)?;
358
359        let go_live_epoch = ctx.accounts.config.go_live_epoch;
360        if go_live_epoch > epoch {
361            msg!(
362                "Priority fee transfer is not live yet. {}/{} - ({:.5})",
363                epoch,
364                go_live_epoch,
365                lamports_to_sol(lamports)
366            );
367
368            return Ok(());
369        }
370
371        // Transfer requested lamports from From to PFDA
372        let ix = solana_program::system_instruction::transfer(
373            ctx.accounts.from.key,
374            &ctx.accounts.priority_fee_distribution_account.key(),
375            lamports,
376        );
377        solana_program::program::invoke(
378            &ix,
379            &[
380                ctx.accounts.from.to_account_info(),
381                ctx.accounts
382                    .priority_fee_distribution_account
383                    .to_account_info(),
384            ],
385        )
386        .map_err(Into::into)
387    }
388}
389
390#[error_code]
391pub enum ErrorCode {
392    #[msg("Account failed validation.")]
393    AccountValidationFailure,
394
395    #[msg("Encountered an arithmetic under/overflow error.")]
396    ArithmeticError,
397
398    #[msg("The maximum number of funds to be claimed has been exceeded.")]
399    ExceedsMaxClaim,
400
401    #[msg("The maximum number of claims has been exceeded.")]
402    ExceedsMaxNumNodes,
403
404    #[msg("The given PriorityFeeDistributionAccount has expired.")]
405    ExpiredPriorityFeeDistributionAccount,
406
407    #[msg("The funds for the given index and PriorityFeeDistributionAccount have already been claimed.")]
408    FundsAlreadyClaimed,
409
410    #[msg("Supplied invalid parameters.")]
411    InvalidParameters,
412
413    #[msg("The given proof is invalid.")]
414    InvalidProof,
415
416    #[msg("Failed to deserialize the supplied vote account data.")]
417    InvalidVoteAccountData,
418
419    #[msg("Validator's commission basis points must be less than or equal to the Config account's max_validator_commission_bps.")]
420    MaxValidatorCommissionFeeBpsExceeded,
421
422    #[msg("The given PriorityFeeDistributionAccount is not ready to be closed.")]
423    PrematureClosePriorityFeeDistributionAccount,
424
425    #[msg("The given ClaimStatus account is not ready to be closed.")]
426    PrematureCloseClaimStatus,
427
428    #[msg("Must wait till at least one epoch after the tip distribution account was created to upload the merkle root.")]
429    PrematureMerkleRootUpload,
430
431    #[msg("No merkle root has been uploaded to the given PriorityFeeDistributionAccount.")]
432    RootNotUploaded,
433
434    #[msg("Unauthorized signer.")]
435    Unauthorized,
436
437    #[msg("TDA not valid for migration.")]
438    InvalidTdaForMigration,
439}
440
441#[derive(Accounts)]
442pub struct CloseClaimStatus<'info> {
443    // bypass seed check since owner check prevents attacker from passing in invalid data
444    // account can only be transferred to us if it is zeroed, failing the deserialization check
445    #[account(
446        mut,
447        close = claim_status_payer,
448        constraint = claim_status_payer.key() == claim_status.claim_status_payer
449    )]
450    pub claim_status: Account<'info, ClaimStatus>,
451
452    /// CHECK: This is checked against claim_status in the constraint
453    /// Receiver of the funds.
454    #[account(mut)]
455    pub claim_status_payer: UncheckedAccount<'info>,
456}
457
458#[derive(Accounts)]
459pub struct Initialize<'info> {
460    #[account(
461        init,
462        seeds = [Config::SEED],
463        bump,
464        payer = initializer,
465        space = Config::SIZE,
466        rent_exempt = enforce
467    )]
468    pub config: Account<'info, Config>,
469
470    pub system_program: Program<'info, System>,
471
472    #[account(mut)]
473    pub initializer: Signer<'info>,
474}
475
476#[derive(Accounts)]
477#[instruction(
478    _merkle_root_upload_authority: Pubkey,
479    _validator_commission_bps: u16,
480    _bump: u8
481)]
482pub struct InitializePriorityFeeDistributionAccount<'info> {
483    pub config: Account<'info, Config>,
484
485    #[account(
486        init,
487        seeds = [
488            PriorityFeeDistributionAccount::SEED,
489            validator_vote_account.key().as_ref(),
490            Clock::get().unwrap().epoch.to_le_bytes().as_ref(),
491        ],
492        bump,
493        payer = signer,
494        space = PriorityFeeDistributionAccount::SIZE,
495        rent_exempt = enforce
496    )]
497    pub priority_fee_distribution_account: Account<'info, PriorityFeeDistributionAccount>,
498
499    /// CHECK: Safe because we check the vote program is the owner before deserialization.
500    /// The validator's vote account is used to check this transaction's signer is also the authorized withdrawer.
501    pub validator_vote_account: AccountInfo<'info>,
502
503    /// Must be equal to the supplied validator vote account's authorized withdrawer.
504    #[account(mut)]
505    pub signer: Signer<'info>,
506
507    pub system_program: Program<'info, System>,
508}
509
510#[derive(Accounts)]
511pub struct UpdateConfig<'info> {
512    #[account(mut, rent_exempt = enforce)]
513    pub config: Account<'info, Config>,
514
515    #[account(mut)]
516    pub authority: Signer<'info>,
517}
518
519impl UpdateConfig<'_> {
520    fn auth(ctx: &Context<UpdateConfig>) -> Result<()> {
521        if ctx.accounts.config.authority != ctx.accounts.authority.key() {
522            Err(Unauthorized.into())
523        } else {
524            Ok(())
525        }
526    }
527}
528
529#[derive(Accounts)]
530#[instruction(epoch: u64)]
531pub struct ClosePriorityFeeDistributionAccount<'info> {
532    pub config: Account<'info, Config>,
533
534    /// CHECK: safe see auth fn
535    #[account(mut)]
536    pub expired_funds_account: AccountInfo<'info>,
537
538    #[account(
539        mut,
540        close = validator_vote_account,
541        seeds = [
542            PriorityFeeDistributionAccount::SEED,
543            validator_vote_account.key().as_ref(),
544            epoch.to_le_bytes().as_ref(),
545        ],
546        bump = priority_fee_distribution_account.bump,
547    )]
548    pub priority_fee_distribution_account: Account<'info, PriorityFeeDistributionAccount>,
549
550    /// CHECK: safe see auth fn
551    #[account(mut)]
552    pub validator_vote_account: AccountInfo<'info>,
553
554    /// Anyone can crank this instruction.
555    #[account(mut)]
556    pub signer: Signer<'info>,
557}
558
559impl ClosePriorityFeeDistributionAccount<'_> {
560    fn auth(ctx: &Context<ClosePriorityFeeDistributionAccount>) -> Result<()> {
561        if ctx.accounts.config.expired_funds_account != ctx.accounts.expired_funds_account.key() {
562            Err(Unauthorized.into())
563        } else {
564            Ok(())
565        }
566    }
567}
568
569#[derive(Accounts)]
570#[instruction(_bump: u8, _amount: u64, _proof: Vec<[u8; 32]>)]
571pub struct Claim<'info> {
572    pub config: Account<'info, Config>,
573
574    #[account(mut, rent_exempt = enforce)]
575    pub priority_fee_distribution_account: Account<'info, PriorityFeeDistributionAccount>,
576
577    pub merkle_root_upload_authority: Signer<'info>,
578
579    /// Status of the claim. Used to prevent the same party from claiming multiple times.
580    #[account(
581        init,
582        rent_exempt = enforce,
583        seeds = [
584            ClaimStatus::SEED,
585            claimant.key().as_ref(),
586            priority_fee_distribution_account.key().as_ref()
587        ],
588        bump,
589        space = ClaimStatus::SIZE,
590        payer = payer
591    )]
592    pub claim_status: Account<'info, ClaimStatus>,
593
594    /// CHECK: This is safe.
595    /// Receiver of the funds.
596    #[account(mut)]
597    pub claimant: AccountInfo<'info>,
598
599    /// Who is paying for the claim.
600    #[account(mut)]
601    pub payer: Signer<'info>,
602
603    pub system_program: Program<'info, System>,
604}
605impl Claim<'_> {
606    fn auth(ctx: &Context<Claim>) -> Result<()> {
607        if ctx.accounts.merkle_root_upload_authority.key()
608            != ctx
609                .accounts
610                .priority_fee_distribution_account
611                .merkle_root_upload_authority
612        {
613            Err(Unauthorized.into())
614        } else {
615            Ok(())
616        }
617    }
618}
619
620#[derive(Accounts)]
621pub struct UploadMerkleRoot<'info> {
622    pub config: Account<'info, Config>,
623
624    #[account(mut, rent_exempt = enforce)]
625    pub priority_fee_distribution_account: Account<'info, PriorityFeeDistributionAccount>,
626
627    #[account(mut)]
628    pub merkle_root_upload_authority: Signer<'info>,
629}
630
631impl UploadMerkleRoot<'_> {
632    fn auth(ctx: &Context<UploadMerkleRoot>) -> Result<()> {
633        if ctx.accounts.merkle_root_upload_authority.key()
634            != ctx
635                .accounts
636                .priority_fee_distribution_account
637                .merkle_root_upload_authority
638        {
639            Err(Unauthorized.into())
640        } else {
641            Ok(())
642        }
643    }
644}
645
646#[derive(Accounts)]
647pub struct InitializeMerkleRootUploadConfig<'info> {
648    #[account(mut, rent_exempt = enforce)]
649    pub config: Account<'info, Config>,
650
651    #[account(
652        init,
653        rent_exempt = enforce,
654        seeds = [
655            MerkleRootUploadConfig::SEED,
656        ],
657        bump,
658        space = MerkleRootUploadConfig::SIZE,
659        payer = payer
660    )]
661    pub merkle_root_upload_config: Account<'info, MerkleRootUploadConfig>,
662
663    pub authority: Signer<'info>,
664
665    #[account(mut)]
666    pub payer: Signer<'info>,
667
668    pub system_program: Program<'info, System>,
669}
670
671impl InitializeMerkleRootUploadConfig<'_> {
672    fn auth(ctx: &Context<InitializeMerkleRootUploadConfig>) -> Result<()> {
673        if ctx.accounts.config.authority != ctx.accounts.authority.key() {
674            Err(Unauthorized.into())
675        } else {
676            Ok(())
677        }
678    }
679}
680
681#[derive(Accounts)]
682pub struct UpdateMerkleRootUploadConfig<'info> {
683    #[account(rent_exempt = enforce)]
684    pub config: Account<'info, Config>,
685
686    #[account(
687        mut,
688        seeds = [MerkleRootUploadConfig::SEED],
689        bump,
690        rent_exempt = enforce,
691    )]
692    pub merkle_root_upload_config: Account<'info, MerkleRootUploadConfig>,
693
694    pub authority: Signer<'info>,
695
696    pub system_program: Program<'info, System>,
697}
698
699impl UpdateMerkleRootUploadConfig<'_> {
700    fn auth(ctx: &Context<UpdateMerkleRootUploadConfig>) -> Result<()> {
701        if ctx.accounts.config.authority != ctx.accounts.authority.key() {
702            Err(Unauthorized.into())
703        } else {
704            Ok(())
705        }
706    }
707}
708
709#[derive(Accounts)]
710pub struct MigrateTdaMerkleRootUploadAuthority<'info> {
711    #[account(mut, rent_exempt = enforce)]
712    pub priority_fee_distribution_account: Account<'info, PriorityFeeDistributionAccount>,
713
714    #[account(
715        seeds = [MerkleRootUploadConfig::SEED],
716        bump,
717        rent_exempt = enforce,
718    )]
719    pub merkle_root_upload_config: Account<'info, MerkleRootUploadConfig>,
720}
721
722#[derive(Accounts)]
723pub struct TransferPriorityFeeTips<'info> {
724    #[account(rent_exempt = enforce)]
725    pub config: Account<'info, Config>,
726
727    #[account(
728        mut,
729        rent_exempt = enforce
730    )]
731    pub priority_fee_distribution_account: Account<'info, PriorityFeeDistributionAccount>,
732
733    #[account(mut)]
734    pub from: Signer<'info>,
735    pub system_program: Program<'info, System>,
736}
737
738// Events
739
740#[event]
741pub struct PriorityFeeDistributionAccountInitializedEvent {
742    pub priority_fee_distribution_account: Pubkey,
743}
744
745#[event]
746pub struct ValidatorCommissionBpsUpdatedEvent {
747    pub priority_fee_distribution_account: Pubkey,
748    pub old_commission_bps: u16,
749    pub new_commission_bps: u16,
750}
751
752#[event]
753pub struct MerkleRootUploadAuthorityUpdatedEvent {
754    pub old_authority: Pubkey,
755    pub new_authority: Pubkey,
756}
757
758#[event]
759pub struct ConfigUpdatedEvent {
760    /// Who updated it.
761    authority: Pubkey,
762}
763
764#[event]
765pub struct ClaimedEvent {
766    /// [PriorityFeeDistributionAccount] claimed from.
767    pub priority_fee_distribution_account: Pubkey,
768
769    /// User that paid for the claim, may or may not be the same as claimant.
770    pub payer: Pubkey,
771
772    /// Account that received the funds.
773    pub claimant: Pubkey,
774
775    /// Amount of funds to distribute.
776    pub amount: u64,
777}
778
779#[event]
780pub struct MerkleRootUploadedEvent {
781    /// Who uploaded the root.
782    pub merkle_root_upload_authority: Pubkey,
783
784    /// Where the root was uploaded to.
785    pub priority_fee_distribution_account: Pubkey,
786}
787
788#[event]
789pub struct PriorityFeeDistributionAccountClosedEvent {
790    /// Account where unclaimed funds were transferred to.
791    pub expired_funds_account: Pubkey,
792
793    /// [PriorityFeeDistributionAccount] closed.
794    pub priority_fee_distribution_account: Pubkey,
795
796    /// Unclaimed amount transferred.
797    pub expired_amount: u64,
798}
799
800#[event]
801pub struct ClaimStatusClosedEvent {
802    /// Account where funds were transferred to.
803    pub claim_status_payer: Pubkey,
804
805    /// [ClaimStatus] account that was closed.
806    pub claim_status_account: Pubkey,
807}