@sudobility/contracts 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/artifacts/contracts/Mailer.sol/Mailer.d.ts +963 -0
- package/artifacts/contracts/Mailer.sol/Mailer.dbg.json +1 -1
- package/artifacts/contracts/Mailer.sol/Mailer.json +95 -2
- package/artifacts/contracts/Mailer.sol/artifacts.d.ts +21 -0
- package/artifacts/contracts/MockUSDC.sol/MockUSDC.d.ts +284 -0
- package/artifacts/contracts/MockUSDC.sol/MockUSDC.dbg.json +1 -1
- package/artifacts/contracts/MockUSDC.sol/artifacts.d.ts +21 -0
- package/artifacts/contracts/interfaces/IERC20.sol/IERC20.d.ts +157 -0
- package/artifacts/contracts/interfaces/IERC20.sol/IERC20.dbg.json +1 -1
- package/artifacts/contracts/interfaces/IERC20.sol/artifacts.d.ts +21 -0
- package/dist/evm/typechain-types/Mailer.d.ts +51 -2
- package/dist/evm/typechain-types/Mailer.d.ts.map +1 -1
- package/dist/evm/typechain-types/factories/Mailer__factory.d.ts +72 -1
- package/dist/evm/typechain-types/factories/Mailer__factory.d.ts.map +1 -1
- package/dist/evm/typechain-types/factories/Mailer__factory.js +94 -1
- package/dist/evm/typechain-types/factories/Mailer__factory.js.map +1 -1
- package/dist/unified/typechain-types/Mailer.d.ts +51 -2
- package/dist/unified/typechain-types/Mailer.d.ts.map +1 -1
- package/dist/unified/typechain-types/factories/Mailer__factory.d.ts +72 -1
- package/dist/unified/typechain-types/factories/Mailer__factory.d.ts.map +1 -1
- package/dist/unified/typechain-types/factories/Mailer__factory.js +94 -1
- package/dist/unified/typechain-types/factories/Mailer__factory.js.map +1 -1
- package/dist/unified-esm/typechain-types/Mailer.d.ts +51 -2
- package/dist/unified-esm/typechain-types/Mailer.d.ts.map +1 -1
- package/dist/unified-esm/typechain-types/factories/Mailer__factory.d.ts +72 -1
- package/dist/unified-esm/typechain-types/factories/Mailer__factory.d.ts.map +1 -1
- package/dist/unified-esm/typechain-types/factories/Mailer__factory.js +94 -1
- package/dist/unified-esm/typechain-types/factories/Mailer__factory.js.map +1 -1
- package/package.json +7 -3
- package/programs/mailer/src/lib.rs +314 -78
- package/typechain-types/Mailer.ts +104 -0
- package/typechain-types/factories/Mailer__factory.ts +94 -1
|
@@ -97,6 +97,20 @@ impl Delegation {
|
|
|
97
97
|
pub const LEN: usize = 32 + 1 + 32 + 1; // 66 bytes (max with Some(Pubkey))
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/// Fee discount account for custom fee percentages
|
|
101
|
+
/// Stores discount (0-100) instead of percentage for cleaner default behavior
|
|
102
|
+
/// 0 = no discount (100% fee), 100 = full discount (0% fee, free)
|
|
103
|
+
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
|
|
104
|
+
pub struct FeeDiscount {
|
|
105
|
+
pub account: Pubkey,
|
|
106
|
+
pub discount: u8, // 0-100: 0 = no discount (full fee), 100 = full discount (free)
|
|
107
|
+
pub bump: u8,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
impl FeeDiscount {
|
|
111
|
+
pub const LEN: usize = 32 + 1 + 1; // 34 bytes
|
|
112
|
+
}
|
|
113
|
+
|
|
100
114
|
/// Instructions
|
|
101
115
|
#[derive(BorshSerialize, BorshDeserialize, Debug)]
|
|
102
116
|
pub enum MailerInstruction {
|
|
@@ -198,7 +212,27 @@ pub enum MailerInstruction {
|
|
|
198
212
|
/// 0. `[signer]` Owner
|
|
199
213
|
/// 1. `[writable]` Mailer state account (PDA)
|
|
200
214
|
SetDelegationFee { new_fee: u64 },
|
|
201
|
-
|
|
215
|
+
|
|
216
|
+
/// Set custom fee percentage for a specific address (owner only)
|
|
217
|
+
/// Accounts:
|
|
218
|
+
/// 0. `[signer]` Owner
|
|
219
|
+
/// 1. `[]` Mailer state account (PDA)
|
|
220
|
+
/// 2. `[writable]` Fee discount account (PDA)
|
|
221
|
+
/// 3. `[]` Account to set custom fee for
|
|
222
|
+
/// 4. `[signer]` Payer for account creation
|
|
223
|
+
/// 5. `[]` System program
|
|
224
|
+
SetCustomFeePercentage {
|
|
225
|
+
account: Pubkey,
|
|
226
|
+
percentage: u8, // 0-100: 0 = free, 100 = full fee
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
/// Clear custom fee percentage for a specific address (owner only)
|
|
230
|
+
/// Accounts:
|
|
231
|
+
/// 0. `[signer]` Owner
|
|
232
|
+
/// 1. `[]` Mailer state account (PDA)
|
|
233
|
+
/// 2. `[writable]` Fee discount account (PDA)
|
|
234
|
+
ClearCustomFeePercentage { account: Pubkey },
|
|
235
|
+
|
|
202
236
|
/// Pause the contract (owner only)
|
|
203
237
|
/// Accounts:
|
|
204
238
|
/// 0. `[signer]` Owner
|
|
@@ -260,6 +294,8 @@ pub enum MailerError {
|
|
|
260
294
|
ContractPaused,
|
|
261
295
|
#[error("Contract is not paused")]
|
|
262
296
|
ContractNotPaused,
|
|
297
|
+
#[error("Invalid percentage (must be 0-100)")]
|
|
298
|
+
InvalidPercentage,
|
|
263
299
|
}
|
|
264
300
|
|
|
265
301
|
impl From<MailerError> for ProgramError {
|
|
@@ -301,6 +337,12 @@ pub fn process_instruction(
|
|
|
301
337
|
MailerInstruction::SetDelegationFee { new_fee } => {
|
|
302
338
|
process_set_delegation_fee(program_id, accounts, new_fee)
|
|
303
339
|
}
|
|
340
|
+
MailerInstruction::SetCustomFeePercentage { account, percentage } => {
|
|
341
|
+
process_set_custom_fee_percentage(program_id, accounts, account, percentage)
|
|
342
|
+
}
|
|
343
|
+
MailerInstruction::ClearCustomFeePercentage { account } => {
|
|
344
|
+
process_clear_custom_fee_percentage(program_id, accounts, account)
|
|
345
|
+
}
|
|
304
346
|
MailerInstruction::Pause => {
|
|
305
347
|
process_pause(program_id, accounts)
|
|
306
348
|
}
|
|
@@ -407,6 +449,9 @@ fn process_send(
|
|
|
407
449
|
return Err(MailerError::ContractPaused.into());
|
|
408
450
|
}
|
|
409
451
|
|
|
452
|
+
// Calculate effective fee based on custom discount (if any)
|
|
453
|
+
let effective_fee = calculate_fee_with_discount(program_id, sender.key, accounts, mailer_state.send_fee)?;
|
|
454
|
+
|
|
410
455
|
if revenue_share_to_receiver {
|
|
411
456
|
// Priority mode: full fee with revenue sharing
|
|
412
457
|
|
|
@@ -453,50 +498,53 @@ fn process_send(
|
|
|
453
498
|
drop(claim_data);
|
|
454
499
|
}
|
|
455
500
|
|
|
456
|
-
// Transfer
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
501
|
+
// Transfer effective fee (may be discounted)
|
|
502
|
+
if effective_fee > 0 {
|
|
503
|
+
invoke(
|
|
504
|
+
&spl_token::instruction::transfer(
|
|
505
|
+
token_program.key,
|
|
506
|
+
sender_usdc.key,
|
|
507
|
+
mailer_usdc.key,
|
|
508
|
+
sender.key,
|
|
509
|
+
&[],
|
|
510
|
+
effective_fee,
|
|
511
|
+
)?,
|
|
512
|
+
&[
|
|
513
|
+
sender_usdc.clone(),
|
|
514
|
+
mailer_usdc.clone(),
|
|
515
|
+
sender.clone(),
|
|
516
|
+
token_program.clone(),
|
|
517
|
+
],
|
|
518
|
+
)?;
|
|
473
519
|
|
|
474
|
-
|
|
475
|
-
|
|
520
|
+
// Record revenue shares (only if fee > 0)
|
|
521
|
+
record_shares(recipient_claim, mailer_account, to, effective_fee)?;
|
|
522
|
+
}
|
|
476
523
|
|
|
477
|
-
msg!("Priority mail sent from {} to {}: {} (revenue share enabled, resolve sender: {})", sender.key, to, subject, _resolve_sender_to_name);
|
|
524
|
+
msg!("Priority mail sent from {} to {}: {} (revenue share enabled, resolve sender: {}, effective fee: {})", sender.key, to, subject, _resolve_sender_to_name, effective_fee);
|
|
478
525
|
} else {
|
|
479
526
|
// Standard mode: 10% fee only, no revenue sharing
|
|
480
|
-
|
|
481
|
-
let owner_fee = mailer_state.send_fee / 10; // 10% of send_fee
|
|
527
|
+
let owner_fee = (effective_fee * 10) / 100; // 10% of effective fee
|
|
482
528
|
|
|
483
529
|
// Transfer only owner fee (10%)
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
530
|
+
if owner_fee > 0 {
|
|
531
|
+
invoke(
|
|
532
|
+
&spl_token::instruction::transfer(
|
|
533
|
+
token_program.key,
|
|
534
|
+
sender_usdc.key,
|
|
535
|
+
mailer_usdc.key,
|
|
536
|
+
sender.key,
|
|
537
|
+
&[],
|
|
538
|
+
owner_fee,
|
|
539
|
+
)?,
|
|
540
|
+
&[
|
|
541
|
+
sender_usdc.clone(),
|
|
542
|
+
mailer_usdc.clone(),
|
|
543
|
+
sender.clone(),
|
|
544
|
+
token_program.clone(),
|
|
545
|
+
],
|
|
546
|
+
)?;
|
|
547
|
+
}
|
|
500
548
|
|
|
501
549
|
// Update owner claimable
|
|
502
550
|
let mut mailer_data = mailer_account.try_borrow_mut_data()?;
|
|
@@ -504,7 +552,7 @@ fn process_send(
|
|
|
504
552
|
mailer_state.owner_claimable += owner_fee;
|
|
505
553
|
mailer_state.serialize(&mut &mut mailer_data[8..])?;
|
|
506
554
|
|
|
507
|
-
msg!("Standard mail sent from {} to {}: {} (resolve sender: {})", sender.key, to, subject, _resolve_sender_to_name);
|
|
555
|
+
msg!("Standard mail sent from {} to {}: {} (resolve sender: {}, effective fee: {})", sender.key, to, subject, _resolve_sender_to_name, effective_fee);
|
|
508
556
|
}
|
|
509
557
|
|
|
510
558
|
Ok(())
|
|
@@ -539,28 +587,33 @@ fn process_send_to_email(
|
|
|
539
587
|
return Err(MailerError::ContractPaused.into());
|
|
540
588
|
}
|
|
541
589
|
|
|
590
|
+
// Calculate effective fee based on custom discount (if any)
|
|
591
|
+
let effective_fee = calculate_fee_with_discount(_program_id, sender.key, accounts, mailer_state.send_fee)?;
|
|
592
|
+
|
|
542
593
|
// Calculate 10% owner fee (no revenue share since no wallet address)
|
|
543
|
-
let owner_fee = (
|
|
594
|
+
let owner_fee = (effective_fee * 10) / 100;
|
|
544
595
|
|
|
545
596
|
// Transfer fee from sender to mailer
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
597
|
+
if owner_fee > 0 {
|
|
598
|
+
let transfer_ix = spl_token::instruction::transfer(
|
|
599
|
+
token_program.key,
|
|
600
|
+
sender_usdc.key,
|
|
601
|
+
mailer_usdc.key,
|
|
602
|
+
sender.key,
|
|
603
|
+
&[],
|
|
604
|
+
owner_fee,
|
|
605
|
+
)?;
|
|
554
606
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
607
|
+
invoke(
|
|
608
|
+
&transfer_ix,
|
|
609
|
+
&[
|
|
610
|
+
sender_usdc.clone(),
|
|
611
|
+
mailer_usdc.clone(),
|
|
612
|
+
sender.clone(),
|
|
613
|
+
token_program.clone(),
|
|
614
|
+
],
|
|
615
|
+
)?;
|
|
616
|
+
}
|
|
564
617
|
|
|
565
618
|
// Update owner claimable
|
|
566
619
|
let mut mailer_data = mailer_account.try_borrow_mut_data()?;
|
|
@@ -568,7 +621,7 @@ fn process_send_to_email(
|
|
|
568
621
|
mailer_state.owner_claimable += owner_fee;
|
|
569
622
|
mailer_state.serialize(&mut &mut mailer_data[8..])?;
|
|
570
623
|
|
|
571
|
-
msg!("Mail sent from {} to email {}: {}", sender.key, to_email, subject);
|
|
624
|
+
msg!("Mail sent from {} to email {}: {} (effective fee: {})", sender.key, to_email, subject, effective_fee);
|
|
572
625
|
|
|
573
626
|
Ok(())
|
|
574
627
|
}
|
|
@@ -601,28 +654,33 @@ fn process_send_prepared_to_email(
|
|
|
601
654
|
return Err(MailerError::ContractPaused.into());
|
|
602
655
|
}
|
|
603
656
|
|
|
657
|
+
// Calculate effective fee based on custom discount (if any)
|
|
658
|
+
let effective_fee = calculate_fee_with_discount(_program_id, sender.key, accounts, mailer_state.send_fee)?;
|
|
659
|
+
|
|
604
660
|
// Calculate 10% owner fee (no revenue share since no wallet address)
|
|
605
|
-
let owner_fee = (
|
|
661
|
+
let owner_fee = (effective_fee * 10) / 100;
|
|
606
662
|
|
|
607
663
|
// Transfer fee from sender to mailer
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
664
|
+
if owner_fee > 0 {
|
|
665
|
+
let transfer_ix = spl_token::instruction::transfer(
|
|
666
|
+
token_program.key,
|
|
667
|
+
sender_usdc.key,
|
|
668
|
+
mailer_usdc.key,
|
|
669
|
+
sender.key,
|
|
670
|
+
&[],
|
|
671
|
+
owner_fee,
|
|
672
|
+
)?;
|
|
616
673
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
674
|
+
invoke(
|
|
675
|
+
&transfer_ix,
|
|
676
|
+
&[
|
|
677
|
+
sender_usdc.clone(),
|
|
678
|
+
mailer_usdc.clone(),
|
|
679
|
+
sender.clone(),
|
|
680
|
+
token_program.clone(),
|
|
681
|
+
],
|
|
682
|
+
)?;
|
|
683
|
+
}
|
|
626
684
|
|
|
627
685
|
// Update owner claimable
|
|
628
686
|
let mut mailer_data = mailer_account.try_borrow_mut_data()?;
|
|
@@ -630,7 +688,7 @@ fn process_send_prepared_to_email(
|
|
|
630
688
|
mailer_state.owner_claimable += owner_fee;
|
|
631
689
|
mailer_state.serialize(&mut &mut mailer_data[8..])?;
|
|
632
690
|
|
|
633
|
-
msg!("Prepared mail sent from {} to email {} (mailId: {})", sender.key, to_email, mail_id);
|
|
691
|
+
msg!("Prepared mail sent from {} to email {} (mailId: {}, effective fee: {})", sender.key, to_email, mail_id, effective_fee);
|
|
634
692
|
|
|
635
693
|
Ok(())
|
|
636
694
|
}
|
|
@@ -951,6 +1009,146 @@ fn process_set_delegation_fee(
|
|
|
951
1009
|
Ok(())
|
|
952
1010
|
}
|
|
953
1011
|
|
|
1012
|
+
/// Set custom fee percentage for a specific address (owner only)
|
|
1013
|
+
fn process_set_custom_fee_percentage(
|
|
1014
|
+
program_id: &Pubkey,
|
|
1015
|
+
accounts: &[AccountInfo],
|
|
1016
|
+
account: Pubkey,
|
|
1017
|
+
percentage: u8,
|
|
1018
|
+
) -> ProgramResult {
|
|
1019
|
+
let account_iter = &mut accounts.iter();
|
|
1020
|
+
let owner = next_account_info(account_iter)?;
|
|
1021
|
+
let mailer_account = next_account_info(account_iter)?;
|
|
1022
|
+
let fee_discount_account = next_account_info(account_iter)?;
|
|
1023
|
+
let _target_account = next_account_info(account_iter)?;
|
|
1024
|
+
let payer = next_account_info(account_iter)?;
|
|
1025
|
+
let system_program = next_account_info(account_iter)?;
|
|
1026
|
+
|
|
1027
|
+
if !owner.is_signer {
|
|
1028
|
+
return Err(ProgramError::MissingRequiredSignature);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// Load mailer state and verify owner
|
|
1032
|
+
let mailer_data = mailer_account.try_borrow_data()?;
|
|
1033
|
+
let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
|
|
1034
|
+
drop(mailer_data);
|
|
1035
|
+
|
|
1036
|
+
if mailer_state.owner != *owner.key {
|
|
1037
|
+
return Err(MailerError::OnlyOwner.into());
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Check if contract is paused
|
|
1041
|
+
if mailer_state.paused {
|
|
1042
|
+
return Err(MailerError::ContractPaused.into());
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// Validate percentage
|
|
1046
|
+
if percentage > 100 {
|
|
1047
|
+
return Err(MailerError::InvalidPercentage.into());
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// Verify fee discount account PDA
|
|
1051
|
+
let (discount_pda, bump) = Pubkey::find_program_address(
|
|
1052
|
+
&[b"discount", account.as_ref()],
|
|
1053
|
+
program_id
|
|
1054
|
+
);
|
|
1055
|
+
|
|
1056
|
+
if fee_discount_account.key != &discount_pda {
|
|
1057
|
+
return Err(MailerError::InvalidPDA.into());
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// Create or update fee discount account
|
|
1061
|
+
if fee_discount_account.lamports() == 0 {
|
|
1062
|
+
let rent = Rent::get()?;
|
|
1063
|
+
let space = 8 + FeeDiscount::LEN;
|
|
1064
|
+
let lamports = rent.minimum_balance(space);
|
|
1065
|
+
|
|
1066
|
+
invoke_signed(
|
|
1067
|
+
&system_instruction::create_account(
|
|
1068
|
+
payer.key,
|
|
1069
|
+
fee_discount_account.key,
|
|
1070
|
+
lamports,
|
|
1071
|
+
space as u64,
|
|
1072
|
+
program_id,
|
|
1073
|
+
),
|
|
1074
|
+
&[payer.clone(), fee_discount_account.clone(), system_program.clone()],
|
|
1075
|
+
&[&[b"discount", account.as_ref(), &[bump]]],
|
|
1076
|
+
)?;
|
|
1077
|
+
|
|
1078
|
+
// Initialize discount account
|
|
1079
|
+
let mut discount_data = fee_discount_account.try_borrow_mut_data()?;
|
|
1080
|
+
discount_data[0..8].copy_from_slice(&hash_discriminator("account:FeeDiscount").to_le_bytes());
|
|
1081
|
+
|
|
1082
|
+
let fee_discount = FeeDiscount {
|
|
1083
|
+
account,
|
|
1084
|
+
discount: 100 - percentage, // Store as discount: 0% fee = 100 discount, 100% fee = 0 discount
|
|
1085
|
+
bump,
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
fee_discount.serialize(&mut &mut discount_data[8..])?;
|
|
1089
|
+
} else {
|
|
1090
|
+
// Update existing discount account
|
|
1091
|
+
let mut discount_data = fee_discount_account.try_borrow_mut_data()?;
|
|
1092
|
+
let mut fee_discount: FeeDiscount = BorshDeserialize::deserialize(&mut &discount_data[8..])?;
|
|
1093
|
+
fee_discount.discount = 100 - percentage; // Store as discount
|
|
1094
|
+
fee_discount.serialize(&mut &mut discount_data[8..])?;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
msg!("Custom fee percentage set for {}: {}%", account, percentage);
|
|
1098
|
+
Ok(())
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/// Clear custom fee percentage for a specific address (owner only)
|
|
1102
|
+
fn process_clear_custom_fee_percentage(
|
|
1103
|
+
program_id: &Pubkey,
|
|
1104
|
+
accounts: &[AccountInfo],
|
|
1105
|
+
account: Pubkey,
|
|
1106
|
+
) -> ProgramResult {
|
|
1107
|
+
let account_iter = &mut accounts.iter();
|
|
1108
|
+
let owner = next_account_info(account_iter)?;
|
|
1109
|
+
let mailer_account = next_account_info(account_iter)?;
|
|
1110
|
+
let fee_discount_account = next_account_info(account_iter)?;
|
|
1111
|
+
|
|
1112
|
+
if !owner.is_signer {
|
|
1113
|
+
return Err(ProgramError::MissingRequiredSignature);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// Load mailer state and verify owner
|
|
1117
|
+
let mailer_data = mailer_account.try_borrow_data()?;
|
|
1118
|
+
let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
|
|
1119
|
+
drop(mailer_data);
|
|
1120
|
+
|
|
1121
|
+
if mailer_state.owner != *owner.key {
|
|
1122
|
+
return Err(MailerError::OnlyOwner.into());
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// Check if contract is paused
|
|
1126
|
+
if mailer_state.paused {
|
|
1127
|
+
return Err(MailerError::ContractPaused.into());
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// Verify fee discount account PDA
|
|
1131
|
+
let (discount_pda, _) = Pubkey::find_program_address(
|
|
1132
|
+
&[b"discount", account.as_ref()],
|
|
1133
|
+
program_id
|
|
1134
|
+
);
|
|
1135
|
+
|
|
1136
|
+
if fee_discount_account.key != &discount_pda {
|
|
1137
|
+
return Err(MailerError::InvalidPDA.into());
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
// Clear by setting discount to 0 (no discount = 100% fee = default behavior)
|
|
1141
|
+
if fee_discount_account.lamports() > 0 {
|
|
1142
|
+
let mut discount_data = fee_discount_account.try_borrow_mut_data()?;
|
|
1143
|
+
let mut fee_discount: FeeDiscount = BorshDeserialize::deserialize(&mut &discount_data[8..])?;
|
|
1144
|
+
fee_discount.discount = 0; // 0 discount = 100% fee = default
|
|
1145
|
+
fee_discount.serialize(&mut &mut discount_data[8..])?;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
msg!("Custom fee percentage cleared for {} (reset to 100%)", account);
|
|
1149
|
+
Ok(())
|
|
1150
|
+
}
|
|
1151
|
+
|
|
954
1152
|
/// Record revenue shares for priority messages
|
|
955
1153
|
fn record_shares(
|
|
956
1154
|
recipient_claim: &AccountInfo,
|
|
@@ -981,6 +1179,44 @@ fn record_shares(
|
|
|
981
1179
|
Ok(())
|
|
982
1180
|
}
|
|
983
1181
|
|
|
1182
|
+
/// Calculate the effective fee for an account based on custom discount
|
|
1183
|
+
/// If no discount account exists, returns full base_fee (default behavior)
|
|
1184
|
+
/// Otherwise applies discount: fee = base_fee * (100 - discount) / 100
|
|
1185
|
+
fn calculate_fee_with_discount(
|
|
1186
|
+
program_id: &Pubkey,
|
|
1187
|
+
account: &Pubkey,
|
|
1188
|
+
accounts: &[AccountInfo],
|
|
1189
|
+
base_fee: u64,
|
|
1190
|
+
) -> Result<u64, ProgramError> {
|
|
1191
|
+
// Try to find fee discount account
|
|
1192
|
+
let (discount_pda, _) = Pubkey::find_program_address(
|
|
1193
|
+
&[b"discount", account.as_ref()],
|
|
1194
|
+
program_id
|
|
1195
|
+
);
|
|
1196
|
+
|
|
1197
|
+
// Check if any account in the accounts slice matches the discount PDA
|
|
1198
|
+
let discount_account = accounts.iter().find(|acc| acc.key == &discount_pda);
|
|
1199
|
+
|
|
1200
|
+
if let Some(discount_acc) = discount_account {
|
|
1201
|
+
// Account exists and has lamports - load the discount
|
|
1202
|
+
if discount_acc.lamports() > 0 {
|
|
1203
|
+
let discount_data = discount_acc.try_borrow_data()?;
|
|
1204
|
+
if discount_data.len() >= 8 + FeeDiscount::LEN {
|
|
1205
|
+
let fee_discount: FeeDiscount = BorshDeserialize::deserialize(&mut &discount_data[8..])?;
|
|
1206
|
+
let discount = fee_discount.discount as u64;
|
|
1207
|
+
|
|
1208
|
+
// Apply discount: fee = base_fee * (100 - discount) / 100
|
|
1209
|
+
// Examples: discount=0 → 100% fee, discount=50 → 50% fee, discount=100 → 0% fee (free)
|
|
1210
|
+
let effective_fee = (base_fee * (100 - discount)) / 100;
|
|
1211
|
+
return Ok(effective_fee);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// No discount account or uninitialized - use full fee (default behavior)
|
|
1217
|
+
Ok(base_fee)
|
|
1218
|
+
}
|
|
1219
|
+
|
|
984
1220
|
/// Pause the contract and distribute owner claimable funds
|
|
985
1221
|
fn process_pause(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
|
|
986
1222
|
let account_iter = &mut accounts.iter();
|