1use anchor_lang::prelude::*;
2
3use crate::TipPaymentError::ArithmeticError;
4
5declare_id!("T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt");
6
7pub const CONFIG_ACCOUNT_SEED: &[u8] = b"CONFIG_ACCOUNT";
13pub const TIP_ACCOUNT_SEED_0: &[u8] = b"TIP_ACCOUNT_0";
14pub const TIP_ACCOUNT_SEED_1: &[u8] = b"TIP_ACCOUNT_1";
15pub const TIP_ACCOUNT_SEED_2: &[u8] = b"TIP_ACCOUNT_2";
16pub const TIP_ACCOUNT_SEED_3: &[u8] = b"TIP_ACCOUNT_3";
17pub const TIP_ACCOUNT_SEED_4: &[u8] = b"TIP_ACCOUNT_4";
18pub const TIP_ACCOUNT_SEED_5: &[u8] = b"TIP_ACCOUNT_5";
19pub const TIP_ACCOUNT_SEED_6: &[u8] = b"TIP_ACCOUNT_6";
20pub const TIP_ACCOUNT_SEED_7: &[u8] = b"TIP_ACCOUNT_7";
21
22pub const HEADER: usize = 8;
23
24#[program]
25pub mod tip_payment {
26 use super::*;
27
28 pub fn initialize(ctx: Context<Initialize>, _bumps: InitBumps) -> Result<()> {
29 let cfg = &mut ctx.accounts.config;
30 cfg.tip_receiver = ctx.accounts.payer.key();
31 cfg.block_builder = ctx.accounts.payer.key();
32
33 let bumps = InitBumps {
34 config: *ctx.bumps.get("config").unwrap(),
35 tip_payment_account_0: *ctx.bumps.get("tip_payment_account_0").unwrap(),
36 tip_payment_account_1: *ctx.bumps.get("tip_payment_account_1").unwrap(),
37 tip_payment_account_2: *ctx.bumps.get("tip_payment_account_2").unwrap(),
38 tip_payment_account_3: *ctx.bumps.get("tip_payment_account_3").unwrap(),
39 tip_payment_account_4: *ctx.bumps.get("tip_payment_account_4").unwrap(),
40 tip_payment_account_5: *ctx.bumps.get("tip_payment_account_5").unwrap(),
41 tip_payment_account_6: *ctx.bumps.get("tip_payment_account_6").unwrap(),
42 tip_payment_account_7: *ctx.bumps.get("tip_payment_account_7").unwrap(),
43 };
44 cfg.bumps = bumps;
45 cfg.block_builder_commission_pct = 0;
46
47 Ok(())
48 }
49
50 pub fn claim_tips(ctx: Context<ClaimTips>) -> Result<()> {
51 let total_tips = TipPaymentAccount::drain_accounts(ctx.accounts.get_tip_accounts())?;
52
53 let block_builder_fee = total_tips
54 .checked_mul(ctx.accounts.config.block_builder_commission_pct)
55 .ok_or(ArithmeticError)?
56 .checked_div(100)
57 .ok_or(ArithmeticError)?;
58
59 let tip_receiver_fee = total_tips
60 .checked_sub(block_builder_fee)
61 .ok_or(ArithmeticError)?;
62
63 if tip_receiver_fee > 0 {
64 **ctx.accounts.tip_receiver.try_borrow_mut_lamports()? = ctx
65 .accounts
66 .tip_receiver
67 .lamports()
68 .checked_add(tip_receiver_fee)
69 .ok_or(ArithmeticError)?;
70 }
71
72 if block_builder_fee > 0 {
73 **ctx.accounts.block_builder.try_borrow_mut_lamports()? = ctx
74 .accounts
75 .block_builder
76 .lamports()
77 .checked_add(block_builder_fee)
78 .ok_or(ArithmeticError)?;
79 }
80
81 if block_builder_fee > 0 || tip_receiver_fee > 0 {
82 emit!(TipsClaimed {
83 tip_receiver: ctx.accounts.tip_receiver.key(),
84 tip_receiver_amount: tip_receiver_fee,
85 block_builder: ctx.accounts.block_builder.key(),
86 block_builder_amount: block_builder_fee,
87 });
88 }
89
90 Ok(())
91 }
92
93 pub fn change_tip_receiver(ctx: Context<ChangeTipReceiver>) -> Result<()> {
96 let total_tips = TipPaymentAccount::drain_accounts(ctx.accounts.get_tip_accounts())?;
97
98 let block_builder_fee = total_tips
99 .checked_mul(ctx.accounts.config.block_builder_commission_pct)
100 .ok_or(ArithmeticError)?
101 .checked_div(100)
102 .ok_or(ArithmeticError)?;
103
104 let tip_receiver_fee = total_tips
105 .checked_sub(block_builder_fee)
106 .ok_or(ArithmeticError)?;
107
108 if tip_receiver_fee > 0 {
109 **ctx.accounts.old_tip_receiver.try_borrow_mut_lamports()? = ctx
110 .accounts
111 .old_tip_receiver
112 .lamports()
113 .checked_add(tip_receiver_fee)
114 .ok_or(ArithmeticError)?;
115 }
116
117 if block_builder_fee > 0 {
118 **ctx.accounts.block_builder.try_borrow_mut_lamports()? = ctx
119 .accounts
120 .block_builder
121 .lamports()
122 .checked_add(block_builder_fee)
123 .ok_or(ArithmeticError)?;
124 }
125
126 if block_builder_fee > 0 || tip_receiver_fee > 0 {
127 emit!(TipsClaimed {
128 tip_receiver: ctx.accounts.old_tip_receiver.key(),
129 tip_receiver_amount: tip_receiver_fee,
130 block_builder: ctx.accounts.block_builder.key(),
131 block_builder_amount: block_builder_fee,
132 });
133 }
134
135 ctx.accounts.config.tip_receiver = ctx.accounts.new_tip_receiver.key();
137 Ok(())
138 }
139
140 pub fn change_block_builder(
144 ctx: Context<ChangeBlockBuilder>,
145 block_builder_commission: u64,
146 ) -> Result<()> {
147 require_gte!(100, block_builder_commission, TipPaymentError::InvalidFee);
148
149 let total_tips = TipPaymentAccount::drain_accounts(ctx.accounts.get_tip_accounts())?;
150
151 let block_builder_fee = total_tips
152 .checked_mul(ctx.accounts.config.block_builder_commission_pct)
153 .ok_or(ArithmeticError)?
154 .checked_div(100)
155 .ok_or(ArithmeticError)?;
156
157 let tip_receiver_fee = total_tips
158 .checked_sub(block_builder_fee)
159 .ok_or(ArithmeticError)?;
160
161 if tip_receiver_fee > 0 {
162 **ctx.accounts.tip_receiver.try_borrow_mut_lamports()? = ctx
163 .accounts
164 .tip_receiver
165 .lamports()
166 .checked_add(tip_receiver_fee)
167 .ok_or(ArithmeticError)?;
168 }
169
170 if block_builder_fee > 0 {
171 **ctx.accounts.old_block_builder.try_borrow_mut_lamports()? = ctx
172 .accounts
173 .old_block_builder
174 .lamports()
175 .checked_add(block_builder_fee)
176 .ok_or(ArithmeticError)?;
177 }
178
179 if block_builder_fee > 0 || tip_receiver_fee > 0 {
180 emit!(TipsClaimed {
181 tip_receiver: ctx.accounts.tip_receiver.key(),
182 tip_receiver_amount: tip_receiver_fee,
183 block_builder: ctx.accounts.old_block_builder.key(),
184 block_builder_amount: block_builder_fee,
185 });
186 }
187
188 ctx.accounts.config.block_builder = ctx.accounts.new_block_builder.key();
190 ctx.accounts.config.block_builder_commission_pct = block_builder_commission;
191 Ok(())
192 }
193}
194
195#[error_code]
196pub enum TipPaymentError {
197 ArithmeticError,
198 InvalidFee,
199}
200
201#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]
203pub struct InitBumps {
204 pub config: u8,
205 pub tip_payment_account_0: u8,
206 pub tip_payment_account_1: u8,
207 pub tip_payment_account_2: u8,
208 pub tip_payment_account_3: u8,
209 pub tip_payment_account_4: u8,
210 pub tip_payment_account_5: u8,
211 pub tip_payment_account_6: u8,
212 pub tip_payment_account_7: u8,
213}
214
215impl InitBumps {
216 const SIZE: usize = 9;
217}
218
219#[derive(Accounts)]
220#[instruction(bumps: InitBumps)]
221pub struct Initialize<'info> {
222 #[account(
224 init,
225 seeds = [CONFIG_ACCOUNT_SEED],
226 bump,
227 payer = payer,
228 space = Config::SIZE,
229 rent_exempt = enforce
230 )]
231 pub config: Account<'info, Config>,
232 #[account(
233 init,
234 seeds = [TIP_ACCOUNT_SEED_0],
235 bump,
236 payer = payer,
237 space = TipPaymentAccount::SIZE,
238 rent_exempt = enforce
239 )]
240 pub tip_payment_account_0: Account<'info, TipPaymentAccount>,
241 #[account(
242 init,
243 seeds = [TIP_ACCOUNT_SEED_1],
244 bump,
245 payer = payer,
246 space = TipPaymentAccount::SIZE,
247 rent_exempt = enforce
248 )]
249 pub tip_payment_account_1: Account<'info, TipPaymentAccount>,
250 #[account(
251 init,
252 seeds = [TIP_ACCOUNT_SEED_2],
253 bump,
254 payer = payer,
255 space = TipPaymentAccount::SIZE,
256 rent_exempt = enforce
257 )]
258 pub tip_payment_account_2: Account<'info, TipPaymentAccount>,
259 #[account(
260 init,
261 seeds = [TIP_ACCOUNT_SEED_3],
262 bump,
263 payer = payer,
264 space = TipPaymentAccount::SIZE,
265 rent_exempt = enforce
266 )]
267 pub tip_payment_account_3: Account<'info, TipPaymentAccount>,
268 #[account(
269 init,
270 seeds = [TIP_ACCOUNT_SEED_4],
271 bump,
272 payer = payer,
273 space = TipPaymentAccount::SIZE,
274 rent_exempt = enforce
275 )]
276 pub tip_payment_account_4: Account<'info, TipPaymentAccount>,
277 #[account(
278 init,
279 seeds = [TIP_ACCOUNT_SEED_5],
280 bump,
281 payer = payer,
282 space = TipPaymentAccount::SIZE,
283 rent_exempt = enforce
284 )]
285 pub tip_payment_account_5: Account<'info, TipPaymentAccount>,
286 #[account(
287 init,
288 seeds = [TIP_ACCOUNT_SEED_6],
289 bump,
290 payer = payer,
291 space = TipPaymentAccount::SIZE,
292 rent_exempt = enforce
293 )]
294 pub tip_payment_account_6: Account<'info, TipPaymentAccount>,
295 #[account(
296 init,
297 seeds = [TIP_ACCOUNT_SEED_7],
298 bump,
299 payer = payer,
300 space = TipPaymentAccount::SIZE,
301 rent_exempt = enforce
302 )]
303 pub tip_payment_account_7: Account<'info, TipPaymentAccount>,
304
305 pub system_program: Program<'info, System>,
306 #[account(mut)]
307 pub payer: Signer<'info>,
308}
309
310#[derive(Accounts)]
311pub struct ClaimTips<'info> {
312 #[account(
313 mut,
314 seeds = [CONFIG_ACCOUNT_SEED],
315 bump = config.bumps.config,
316 rent_exempt = enforce
317 )]
318 pub config: Account<'info, Config>,
319 #[account(
320 mut,
321 seeds = [TIP_ACCOUNT_SEED_0],
322 bump = config.bumps.tip_payment_account_0,
323 rent_exempt = enforce
324 )]
325 pub tip_payment_account_0: Account<'info, TipPaymentAccount>,
326 #[account(
327 mut,
328 seeds = [TIP_ACCOUNT_SEED_1],
329 bump = config.bumps.tip_payment_account_1,
330 rent_exempt = enforce
331 )]
332 pub tip_payment_account_1: Account<'info, TipPaymentAccount>,
333 #[account(
334 mut,
335 seeds = [TIP_ACCOUNT_SEED_2],
336 bump = config.bumps.tip_payment_account_2,
337 rent_exempt = enforce
338 )]
339 pub tip_payment_account_2: Account<'info, TipPaymentAccount>,
340 #[account(
341 mut,
342 seeds = [TIP_ACCOUNT_SEED_3],
343 bump = config.bumps.tip_payment_account_3,
344 rent_exempt = enforce
345 )]
346 pub tip_payment_account_3: Account<'info, TipPaymentAccount>,
347 #[account(
348 mut,
349 seeds = [TIP_ACCOUNT_SEED_4],
350 bump = config.bumps.tip_payment_account_4,
351 rent_exempt = enforce
352 )]
353 pub tip_payment_account_4: Account<'info, TipPaymentAccount>,
354 #[account(
355 mut,
356 seeds = [TIP_ACCOUNT_SEED_5],
357 bump = config.bumps.tip_payment_account_5,
358 rent_exempt = enforce
359 )]
360 pub tip_payment_account_5: Account<'info, TipPaymentAccount>,
361 #[account(
362 mut,
363 seeds = [TIP_ACCOUNT_SEED_6],
364 bump = config.bumps.tip_payment_account_6,
365 rent_exempt = enforce
366 )]
367 pub tip_payment_account_6: Account<'info, TipPaymentAccount>,
368 #[account(
369 mut,
370 seeds = [TIP_ACCOUNT_SEED_7],
371 bump = config.bumps.tip_payment_account_7,
372 rent_exempt = enforce
373 )]
374 pub tip_payment_account_7: Account<'info, TipPaymentAccount>,
375
376 #[account(
379 mut,
380 constraint = config.tip_receiver == tip_receiver.key(),
381 )]
382 pub tip_receiver: AccountInfo<'info>,
383
384 #[account(
386 mut,
387 constraint = config.block_builder == block_builder.key(),
388 )]
389 pub block_builder: AccountInfo<'info>,
390
391 #[account(mut)]
392 pub signer: Signer<'info>,
393}
394
395impl<'info> ClaimTips<'info> {
396 fn get_tip_accounts(&self) -> Vec<AccountInfo<'info>> {
397 vec![
398 self.tip_payment_account_0.to_account_info(),
399 self.tip_payment_account_1.to_account_info(),
400 self.tip_payment_account_2.to_account_info(),
401 self.tip_payment_account_3.to_account_info(),
402 self.tip_payment_account_4.to_account_info(),
403 self.tip_payment_account_5.to_account_info(),
404 self.tip_payment_account_6.to_account_info(),
405 self.tip_payment_account_7.to_account_info(),
406 ]
407 }
408}
409
410#[derive(Accounts)]
411pub struct ChangeTipReceiver<'info> {
412 #[account(mut)]
413 pub config: Account<'info, Config>,
414
415 #[account(mut, constraint = old_tip_receiver.key() == config.tip_receiver)]
418 pub old_tip_receiver: AccountInfo<'info>,
419
420 #[account(mut)]
422 pub new_tip_receiver: AccountInfo<'info>,
423
424 #[account(mut, constraint = block_builder.key() == config.block_builder)]
427 pub block_builder: AccountInfo<'info>,
428
429 #[account(
430 mut,
431 seeds = [TIP_ACCOUNT_SEED_0],
432 bump = config.bumps.tip_payment_account_0,
433 rent_exempt = enforce
434 )]
435 pub tip_payment_account_0: Account<'info, TipPaymentAccount>,
436
437 #[account(
438 mut,
439 seeds = [TIP_ACCOUNT_SEED_1],
440 bump = config.bumps.tip_payment_account_1,
441 rent_exempt = enforce
442 )]
443 pub tip_payment_account_1: Account<'info, TipPaymentAccount>,
444
445 #[account(
446 mut,
447 seeds = [TIP_ACCOUNT_SEED_2],
448 bump = config.bumps.tip_payment_account_2,
449 rent_exempt = enforce
450 )]
451 pub tip_payment_account_2: Account<'info, TipPaymentAccount>,
452
453 #[account(
454 mut,
455 seeds = [TIP_ACCOUNT_SEED_3],
456 bump = config.bumps.tip_payment_account_3,
457 rent_exempt = enforce
458 )]
459 pub tip_payment_account_3: Account<'info, TipPaymentAccount>,
460
461 #[account(
462 mut,
463 seeds = [TIP_ACCOUNT_SEED_4],
464 bump = config.bumps.tip_payment_account_4,
465 rent_exempt = enforce
466 )]
467 pub tip_payment_account_4: Account<'info, TipPaymentAccount>,
468
469 #[account(
470 mut,
471 seeds = [TIP_ACCOUNT_SEED_5],
472 bump = config.bumps.tip_payment_account_5,
473 rent_exempt = enforce
474 )]
475 pub tip_payment_account_5: Account<'info, TipPaymentAccount>,
476
477 #[account(
478 mut,
479 seeds = [TIP_ACCOUNT_SEED_6],
480 bump = config.bumps.tip_payment_account_6,
481 rent_exempt = enforce
482 )]
483 pub tip_payment_account_6: Account<'info, TipPaymentAccount>,
484
485 #[account(
486 mut,
487 seeds = [TIP_ACCOUNT_SEED_7],
488 bump = config.bumps.tip_payment_account_7,
489 rent_exempt = enforce
490 )]
491 pub tip_payment_account_7: Account<'info, TipPaymentAccount>,
492
493 #[account(mut)]
494 pub signer: Signer<'info>,
495}
496
497impl<'info> ChangeTipReceiver<'info> {
498 fn get_tip_accounts(&self) -> Vec<AccountInfo<'info>> {
499 vec![
500 self.tip_payment_account_0.to_account_info(),
501 self.tip_payment_account_1.to_account_info(),
502 self.tip_payment_account_2.to_account_info(),
503 self.tip_payment_account_3.to_account_info(),
504 self.tip_payment_account_4.to_account_info(),
505 self.tip_payment_account_5.to_account_info(),
506 self.tip_payment_account_6.to_account_info(),
507 self.tip_payment_account_7.to_account_info(),
508 ]
509 }
510}
511
512#[derive(Accounts)]
513pub struct ChangeBlockBuilder<'info> {
514 #[account(mut)]
515 pub config: Account<'info, Config>,
516
517 #[account(mut, constraint = tip_receiver.key() == config.tip_receiver)]
520 pub tip_receiver: AccountInfo<'info>,
521
522 #[account(mut, constraint = old_block_builder.key() == config.block_builder)]
525 pub old_block_builder: AccountInfo<'info>,
526
527 #[account(mut)]
529 pub new_block_builder: AccountInfo<'info>,
530
531 #[account(
532 mut,
533 seeds = [TIP_ACCOUNT_SEED_0],
534 bump = config.bumps.tip_payment_account_0,
535 rent_exempt = enforce
536 )]
537 pub tip_payment_account_0: Account<'info, TipPaymentAccount>,
538
539 #[account(
540 mut,
541 seeds = [TIP_ACCOUNT_SEED_1],
542 bump = config.bumps.tip_payment_account_1,
543 rent_exempt = enforce
544 )]
545 pub tip_payment_account_1: Account<'info, TipPaymentAccount>,
546
547 #[account(
548 mut,
549 seeds = [TIP_ACCOUNT_SEED_2],
550 bump = config.bumps.tip_payment_account_2,
551 rent_exempt = enforce
552 )]
553 pub tip_payment_account_2: Account<'info, TipPaymentAccount>,
554
555 #[account(
556 mut,
557 seeds = [TIP_ACCOUNT_SEED_3],
558 bump = config.bumps.tip_payment_account_3,
559 rent_exempt = enforce
560 )]
561 pub tip_payment_account_3: Account<'info, TipPaymentAccount>,
562
563 #[account(
564 mut,
565 seeds = [TIP_ACCOUNT_SEED_4],
566 bump = config.bumps.tip_payment_account_4,
567 rent_exempt = enforce
568 )]
569 pub tip_payment_account_4: Account<'info, TipPaymentAccount>,
570
571 #[account(
572 mut,
573 seeds = [TIP_ACCOUNT_SEED_5],
574 bump = config.bumps.tip_payment_account_5,
575 rent_exempt = enforce
576 )]
577 pub tip_payment_account_5: Account<'info, TipPaymentAccount>,
578
579 #[account(
580 mut,
581 seeds = [TIP_ACCOUNT_SEED_6],
582 bump = config.bumps.tip_payment_account_6,
583 rent_exempt = enforce
584 )]
585 pub tip_payment_account_6: Account<'info, TipPaymentAccount>,
586
587 #[account(
588 mut,
589 seeds = [TIP_ACCOUNT_SEED_7],
590 bump = config.bumps.tip_payment_account_7,
591 rent_exempt = enforce
592 )]
593 pub tip_payment_account_7: Account<'info, TipPaymentAccount>,
594
595 #[account(mut)]
596 pub signer: Signer<'info>,
597}
598
599impl<'info> ChangeBlockBuilder<'info> {
600 fn get_tip_accounts(&self) -> Vec<AccountInfo<'info>> {
601 vec![
602 self.tip_payment_account_0.to_account_info(),
603 self.tip_payment_account_1.to_account_info(),
604 self.tip_payment_account_2.to_account_info(),
605 self.tip_payment_account_3.to_account_info(),
606 self.tip_payment_account_4.to_account_info(),
607 self.tip_payment_account_5.to_account_info(),
608 self.tip_payment_account_6.to_account_info(),
609 self.tip_payment_account_7.to_account_info(),
610 ]
611 }
612}
613
614#[account]
616#[derive(Default)]
617pub struct Config {
618 pub tip_receiver: Pubkey,
620
621 pub block_builder: Pubkey,
623 pub block_builder_commission_pct: u64,
624
625 pub bumps: InitBumps,
627}
628
629impl Config {
630 pub const SIZE: usize = 8 + 32 + 32 + 8 + InitBumps::SIZE;
632}
633
634#[account]
637#[derive(Default)]
638pub struct TipPaymentAccount {}
639
640impl TipPaymentAccount {
641 pub const SIZE: usize = 8;
642
643 fn drain_accounts(accs: Vec<AccountInfo>) -> Result<u64> {
644 let mut total_tips: u64 = 0;
645 for acc in accs {
646 total_tips = total_tips
647 .checked_add(Self::drain_account(&acc)?)
648 .ok_or(ArithmeticError)?;
649 }
650
651 Ok(total_tips)
652 }
653
654 fn drain_account(acc: &AccountInfo) -> Result<u64> {
655 let tips = Self::calc_tips(acc.lamports())?;
656 if tips > 0 {
657 let pre_lamports = acc.lamports();
658 **acc.try_borrow_mut_lamports()? =
659 pre_lamports.checked_sub(tips).ok_or(ArithmeticError)?;
660 }
661 Ok(tips)
662 }
663
664 fn calc_tips(total_balance: u64) -> Result<u64> {
665 let rent = Rent::get()?;
666 let min_rent = rent.minimum_balance(Self::SIZE);
667
668 Ok(total_balance.checked_sub(min_rent).ok_or(ArithmeticError)?)
669 }
670}
671
672#[event]
674pub struct TipsClaimed {
675 tip_receiver: Pubkey,
676 tip_receiver_amount: u64,
677 block_builder: Pubkey,
678 block_builder_amount: u64,
679}