@sudobility/contracts 0.14.0 → 1.9.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.
Files changed (110) hide show
  1. package/README.md +2 -3
  2. package/artifacts/contracts/Mailer.sol/Mailer.dbg.json +1 -1
  3. package/artifacts/contracts/Mailer.sol/Mailer.json +2 -278
  4. package/artifacts/contracts/MockUSDC.sol/MockUSDC.dbg.json +1 -1
  5. package/artifacts/contracts/interfaces/IERC20.sol/IERC20.dbg.json +1 -1
  6. package/dist/evm/src/evm/index.d.ts +1 -1
  7. package/dist/evm/src/evm/index.d.ts.map +1 -1
  8. package/dist/evm/src/evm/index.js +1 -1
  9. package/dist/evm/src/evm/index.js.map +1 -1
  10. package/dist/evm/src/evm/mailer-client.d.ts +180 -936
  11. package/dist/evm/src/evm/mailer-client.d.ts.map +1 -1
  12. package/dist/evm/src/evm/mailer-client.js +249 -451
  13. package/dist/evm/src/evm/mailer-client.js.map +1 -1
  14. package/dist/evm/typechain-types/Mailer.d.ts +10 -177
  15. package/dist/evm/typechain-types/Mailer.d.ts.map +1 -1
  16. package/dist/evm/typechain-types/factories/Mailer__factory.d.ts +1 -216
  17. package/dist/evm/typechain-types/factories/Mailer__factory.d.ts.map +1 -1
  18. package/dist/evm/typechain-types/factories/Mailer__factory.js +1 -277
  19. package/dist/evm/typechain-types/factories/Mailer__factory.js.map +1 -1
  20. package/dist/solana/solana/index.d.ts +1 -1
  21. package/dist/solana/solana/index.d.ts.map +1 -1
  22. package/dist/solana/solana/index.js +1 -3
  23. package/dist/solana/solana/index.js.map +1 -1
  24. package/dist/solana/solana/mailer-client.d.ts +18 -91
  25. package/dist/solana/solana/mailer-client.d.ts.map +1 -1
  26. package/dist/solana/solana/mailer-client.js +55 -316
  27. package/dist/solana/solana/mailer-client.js.map +1 -1
  28. package/dist/unified/src/evm/index.d.ts +1 -1
  29. package/dist/unified/src/evm/index.d.ts.map +1 -1
  30. package/dist/unified/src/evm/index.js +1 -1
  31. package/dist/unified/src/evm/index.js.map +1 -1
  32. package/dist/unified/src/evm/mailer-client.d.ts +180 -936
  33. package/dist/unified/src/evm/mailer-client.d.ts.map +1 -1
  34. package/dist/unified/src/evm/mailer-client.js +249 -451
  35. package/dist/unified/src/evm/mailer-client.js.map +1 -1
  36. package/dist/unified/src/react/hooks/useMailerMutations.d.ts +1 -1
  37. package/dist/unified/src/react/hooks/useMailerMutations.js +1 -1
  38. package/dist/unified/src/solana/index.d.ts +1 -1
  39. package/dist/unified/src/solana/index.d.ts.map +1 -1
  40. package/dist/unified/src/solana/index.js +1 -3
  41. package/dist/unified/src/solana/index.js.map +1 -1
  42. package/dist/unified/src/solana/mailer-client.d.ts +18 -91
  43. package/dist/unified/src/solana/mailer-client.d.ts.map +1 -1
  44. package/dist/unified/src/solana/mailer-client.js +55 -316
  45. package/dist/unified/src/solana/mailer-client.js.map +1 -1
  46. package/dist/unified/src/unified/index.d.ts +1 -1
  47. package/dist/unified/src/unified/index.d.ts.map +1 -1
  48. package/dist/unified/src/unified/onchain-mailer-client.d.ts +251 -111
  49. package/dist/unified/src/unified/onchain-mailer-client.d.ts.map +1 -1
  50. package/dist/unified/src/unified/onchain-mailer-client.js +1375 -744
  51. package/dist/unified/src/unified/onchain-mailer-client.js.map +1 -1
  52. package/dist/unified/src/unified/types.d.ts +6 -16
  53. package/dist/unified/src/unified/types.d.ts.map +1 -1
  54. package/dist/unified/src/utils/chain-config.d.ts +2 -4
  55. package/dist/unified/src/utils/chain-config.d.ts.map +1 -1
  56. package/dist/unified/src/utils/chain-config.js +36 -46
  57. package/dist/unified/src/utils/chain-config.js.map +1 -1
  58. package/dist/unified/typechain-types/Mailer.d.ts +10 -177
  59. package/dist/unified/typechain-types/Mailer.d.ts.map +1 -1
  60. package/dist/unified/typechain-types/factories/Mailer__factory.d.ts +1 -216
  61. package/dist/unified/typechain-types/factories/Mailer__factory.d.ts.map +1 -1
  62. package/dist/unified/typechain-types/factories/Mailer__factory.js +1 -277
  63. package/dist/unified/typechain-types/factories/Mailer__factory.js.map +1 -1
  64. package/dist/unified-esm/src/evm/index.d.ts +1 -1
  65. package/dist/unified-esm/src/evm/index.d.ts.map +1 -1
  66. package/dist/unified-esm/src/evm/index.js +1 -1
  67. package/dist/unified-esm/src/evm/index.js.map +1 -1
  68. package/dist/unified-esm/src/evm/mailer-client.d.ts +180 -936
  69. package/dist/unified-esm/src/evm/mailer-client.d.ts.map +1 -1
  70. package/dist/unified-esm/src/evm/mailer-client.js +251 -453
  71. package/dist/unified-esm/src/evm/mailer-client.js.map +1 -1
  72. package/dist/unified-esm/src/react/hooks/useMailerMutations.d.ts +1 -1
  73. package/dist/unified-esm/src/react/hooks/useMailerMutations.js +1 -1
  74. package/dist/unified-esm/src/solana/index.d.ts +1 -1
  75. package/dist/unified-esm/src/solana/index.d.ts.map +1 -1
  76. package/dist/unified-esm/src/solana/index.js +1 -1
  77. package/dist/unified-esm/src/solana/index.js.map +1 -1
  78. package/dist/unified-esm/src/solana/mailer-client.d.ts +18 -91
  79. package/dist/unified-esm/src/solana/mailer-client.d.ts.map +1 -1
  80. package/dist/unified-esm/src/solana/mailer-client.js +56 -317
  81. package/dist/unified-esm/src/solana/mailer-client.js.map +1 -1
  82. package/dist/unified-esm/src/unified/index.d.ts +1 -1
  83. package/dist/unified-esm/src/unified/index.d.ts.map +1 -1
  84. package/dist/unified-esm/src/unified/onchain-mailer-client.d.ts +251 -111
  85. package/dist/unified-esm/src/unified/onchain-mailer-client.d.ts.map +1 -1
  86. package/dist/unified-esm/src/unified/onchain-mailer-client.js +1379 -748
  87. package/dist/unified-esm/src/unified/onchain-mailer-client.js.map +1 -1
  88. package/dist/unified-esm/src/unified/types.d.ts +6 -16
  89. package/dist/unified-esm/src/unified/types.d.ts.map +1 -1
  90. package/dist/unified-esm/src/utils/chain-config.d.ts +2 -4
  91. package/dist/unified-esm/src/utils/chain-config.d.ts.map +1 -1
  92. package/dist/unified-esm/src/utils/chain-config.js +35 -46
  93. package/dist/unified-esm/src/utils/chain-config.js.map +1 -1
  94. package/dist/unified-esm/typechain-types/Mailer.d.ts +10 -177
  95. package/dist/unified-esm/typechain-types/Mailer.d.ts.map +1 -1
  96. package/dist/unified-esm/typechain-types/factories/Mailer__factory.d.ts +1 -216
  97. package/dist/unified-esm/typechain-types/factories/Mailer__factory.d.ts.map +1 -1
  98. package/dist/unified-esm/typechain-types/factories/Mailer__factory.js +1 -277
  99. package/dist/unified-esm/typechain-types/factories/Mailer__factory.js.map +1 -1
  100. package/package.json +9 -20
  101. package/programs/mailer/src/lib.rs +171 -1026
  102. package/programs/mailer/tests/integration_tests.rs +65 -586
  103. package/typechain-types/Mailer.ts +8 -319
  104. package/typechain-types/factories/Mailer__factory.ts +1 -277
  105. package/artifacts/contracts/Mailer.sol/Mailer.d.ts +0 -1146
  106. package/artifacts/contracts/Mailer.sol/artifacts.d.ts +0 -21
  107. package/artifacts/contracts/MockUSDC.sol/MockUSDC.d.ts +0 -284
  108. package/artifacts/contracts/MockUSDC.sol/artifacts.d.ts +0 -21
  109. package/artifacts/contracts/interfaces/IERC20.sol/IERC20.d.ts +0 -157
  110. package/artifacts/contracts/interfaces/IERC20.sol/artifacts.d.ts +0 -21
@@ -1,6 +1,6 @@
1
1
  //! # Native Solana Mailer Program
2
2
  //!
3
- //! A native Solana program for decentralized messaging with delegation management,
3
+ //! A native Solana program for decentralized messaging with delegation management,
4
4
  //! USDC fees and revenue sharing - no Anchor dependencies.
5
5
  //!
6
6
  //! ## Key Features
@@ -25,6 +25,7 @@
25
25
  //! - Standard: Sender pays 10% fee only
26
26
  //! - Owner gets 10% of all fees
27
27
 
28
+
28
29
  use borsh::{BorshDeserialize, BorshSerialize};
29
30
  use solana_program::{
30
31
  account_info::{next_account_info, AccountInfo},
@@ -33,13 +34,11 @@ use solana_program::{
33
34
  msg,
34
35
  program::{invoke, invoke_signed},
35
36
  program_error::ProgramError,
36
- program_pack::Pack,
37
37
  pubkey::Pubkey,
38
38
  rent::Rent,
39
39
  system_instruction,
40
40
  sysvar::Sysvar,
41
41
  };
42
- use spl_token::state::Account as TokenAccount;
43
42
  use thiserror::Error;
44
43
 
45
44
  // Program ID for the Native Mailer program
@@ -98,20 +97,6 @@ impl Delegation {
98
97
  pub const LEN: usize = 32 + 1 + 32 + 1; // 66 bytes (max with Some(Pubkey))
99
98
  }
100
99
 
101
- /// Fee discount account for custom fee percentages
102
- /// Stores discount (0-100) instead of percentage for cleaner default behavior
103
- /// 0 = no discount (100% fee), 100 = full discount (0% fee, free)
104
- #[derive(BorshSerialize, BorshDeserialize, Debug, Clone)]
105
- pub struct FeeDiscount {
106
- pub account: Pubkey,
107
- pub discount: u8, // 0-100: 0 = no discount (full fee), 100 = full discount (free)
108
- pub bump: u8,
109
- }
110
-
111
- impl FeeDiscount {
112
- pub const LEN: usize = 32 + 1 + 1; // 34 bytes
113
- }
114
-
115
100
  /// Instructions
116
101
  #[derive(BorshSerialize, BorshDeserialize, Debug)]
117
102
  pub enum MailerInstruction {
@@ -139,22 +124,6 @@ pub enum MailerInstruction {
139
124
  resolve_sender_to_name: bool,
140
125
  },
141
126
 
142
- /// Send prepared message with optional revenue sharing (references off-chain content via mailId)
143
- /// Accounts:
144
- /// 0. `[signer]` Sender
145
- /// 1. `[writable]` Recipient claim account (PDA)
146
- /// 2. `[]` Mailer state account (PDA)
147
- /// 3. `[writable]` Sender USDC account
148
- /// 4. `[writable]` Mailer USDC account
149
- /// 5. `[]` Token program
150
- /// 6. `[]` System program
151
- SendPrepared {
152
- to: Pubkey,
153
- mail_id: String,
154
- revenue_share_to_receiver: bool,
155
- resolve_sender_to_name: bool,
156
- },
157
-
158
127
  /// Send message to email address (no wallet address known)
159
128
  /// Charges only 10% owner fee since recipient wallet is unknown
160
129
  /// Accounts:
@@ -177,22 +146,9 @@ pub enum MailerInstruction {
177
146
  /// 2. `[writable]` Sender USDC account
178
147
  /// 3. `[writable]` Mailer USDC account
179
148
  /// 4. `[]` Token program
180
- SendPreparedToEmail { to_email: String, mail_id: String },
181
-
182
- /// Send message through webhook (referenced by webhookId)
183
- /// Accounts:
184
- /// 0. `[signer]` Sender
185
- /// 1. `[writable]` Recipient claim account (PDA)
186
- /// 2. `[]` Mailer state account (PDA)
187
- /// 3. `[writable]` Sender USDC account
188
- /// 4. `[writable]` Mailer USDC account
189
- /// 5. `[]` Token program
190
- /// 6. `[]` System program
191
- SendThroughWebhook {
192
- to: Pubkey,
193
- webhook_id: String,
194
- revenue_share_to_receiver: bool,
195
- resolve_sender_to_name: bool,
149
+ SendPreparedToEmail {
150
+ to_email: String,
151
+ mail_id: String,
196
152
  },
197
153
 
198
154
  /// Claim recipient share
@@ -235,7 +191,6 @@ pub enum MailerInstruction {
235
191
  /// Accounts:
236
192
  /// 0. `[signer]` Rejector
237
193
  /// 1. `[writable]` Delegation account (PDA)
238
- /// 2. `[]` Mailer state account (PDA)
239
194
  RejectDelegation,
240
195
 
241
196
  /// Set delegation fee (owner only)
@@ -243,27 +198,7 @@ pub enum MailerInstruction {
243
198
  /// 0. `[signer]` Owner
244
199
  /// 1. `[writable]` Mailer state account (PDA)
245
200
  SetDelegationFee { new_fee: u64 },
246
-
247
- /// Set custom fee percentage for a specific address (owner only)
248
- /// Accounts:
249
- /// 0. `[signer]` Owner
250
- /// 1. `[]` Mailer state account (PDA)
251
- /// 2. `[writable]` Fee discount account (PDA)
252
- /// 3. `[]` Account to set custom fee for
253
- /// 4. `[signer]` Payer for account creation
254
- /// 5. `[]` System program
255
- SetCustomFeePercentage {
256
- account: Pubkey,
257
- percentage: u8, // 0-100: 0 = free, 100 = full fee
258
- },
259
-
260
- /// Clear custom fee percentage for a specific address (owner only)
261
- /// Accounts:
262
- /// 0. `[signer]` Owner
263
- /// 1. `[]` Mailer state account (PDA)
264
- /// 2. `[writable]` Fee discount account (PDA)
265
- ClearCustomFeePercentage { account: Pubkey },
266
-
201
+
267
202
  /// Pause the contract (owner only)
268
203
  /// Accounts:
269
204
  /// 0. `[signer]` Owner
@@ -272,13 +207,13 @@ pub enum MailerInstruction {
272
207
  /// 3. `[writable]` Mailer USDC account
273
208
  /// 4. `[]` Token program
274
209
  Pause,
275
-
210
+
276
211
  /// Unpause the contract (owner only)
277
212
  /// Accounts:
278
213
  /// 0. `[signer]` Owner
279
214
  /// 1. `[writable]` Mailer state account (PDA)
280
215
  Unpause,
281
-
216
+
282
217
  /// Distribute claimable funds (when paused)
283
218
  /// Accounts:
284
219
  /// 0. `[signer]` Anyone can call
@@ -289,13 +224,6 @@ pub enum MailerInstruction {
289
224
  /// 5. `[]` Token program
290
225
  DistributeClaimableFunds { recipient: Pubkey },
291
226
 
292
- /// Claim expired recipient shares (owner only)
293
- /// Accounts:
294
- /// 0. `[signer]` Owner
295
- /// 1. `[writable]` Mailer state account (PDA)
296
- /// 2. `[writable]` Recipient claim account (PDA)
297
- ClaimExpiredShares { recipient: Pubkey },
298
-
299
227
  /// Emergency unpause without fund distribution (owner only)
300
228
  /// Accounts:
301
229
  /// 0. `[signer]` Owner
@@ -328,16 +256,10 @@ pub enum MailerError {
328
256
  InvalidPDA,
329
257
  #[error("Invalid account owner")]
330
258
  InvalidAccountOwner,
331
- #[error("Invalid token mint")]
332
- InvalidMint,
333
- #[error("Invalid token program")]
334
- InvalidTokenProgram,
335
259
  #[error("Contract is paused")]
336
260
  ContractPaused,
337
261
  #[error("Contract is not paused")]
338
262
  ContractNotPaused,
339
- #[error("Invalid percentage (must be 0-100)")]
340
- InvalidPercentage,
341
263
  }
342
264
 
343
265
  impl From<MailerError> for ProgramError {
@@ -358,55 +280,15 @@ pub fn process_instruction(
358
280
  MailerInstruction::Initialize { usdc_mint } => {
359
281
  process_initialize(program_id, accounts, usdc_mint)
360
282
  }
361
- MailerInstruction::Send {
362
- to,
363
- subject,
364
- _body,
365
- revenue_share_to_receiver,
366
- resolve_sender_to_name,
367
- } => process_send(
368
- program_id,
369
- accounts,
370
- to,
371
- subject,
372
- _body,
373
- revenue_share_to_receiver,
374
- resolve_sender_to_name,
375
- ),
376
- MailerInstruction::SendPrepared {
377
- to,
378
- mail_id,
379
- revenue_share_to_receiver,
380
- resolve_sender_to_name,
381
- } => process_send_prepared(
382
- program_id,
383
- accounts,
384
- to,
385
- mail_id,
386
- revenue_share_to_receiver,
387
- resolve_sender_to_name,
388
- ),
389
- MailerInstruction::SendToEmail {
390
- to_email,
391
- subject,
392
- _body,
393
- } => process_send_to_email(program_id, accounts, to_email, subject, _body),
283
+ MailerInstruction::Send { to, subject, _body, revenue_share_to_receiver, resolve_sender_to_name } => {
284
+ process_send(program_id, accounts, to, subject, _body, revenue_share_to_receiver, resolve_sender_to_name)
285
+ }
286
+ MailerInstruction::SendToEmail { to_email, subject, _body } => {
287
+ process_send_to_email(program_id, accounts, to_email, subject, _body)
288
+ }
394
289
  MailerInstruction::SendPreparedToEmail { to_email, mail_id } => {
395
290
  process_send_prepared_to_email(program_id, accounts, to_email, mail_id)
396
291
  }
397
- MailerInstruction::SendThroughWebhook {
398
- to,
399
- webhook_id,
400
- revenue_share_to_receiver,
401
- resolve_sender_to_name,
402
- } => process_send_through_webhook(
403
- program_id,
404
- accounts,
405
- to,
406
- webhook_id,
407
- revenue_share_to_receiver,
408
- resolve_sender_to_name,
409
- ),
410
292
  MailerInstruction::ClaimRecipientShare => {
411
293
  process_claim_recipient_share(program_id, accounts)
412
294
  }
@@ -419,22 +301,18 @@ pub fn process_instruction(
419
301
  MailerInstruction::SetDelegationFee { new_fee } => {
420
302
  process_set_delegation_fee(program_id, accounts, new_fee)
421
303
  }
422
- MailerInstruction::SetCustomFeePercentage {
423
- account,
424
- percentage,
425
- } => process_set_custom_fee_percentage(program_id, accounts, account, percentage),
426
- MailerInstruction::ClearCustomFeePercentage { account } => {
427
- process_clear_custom_fee_percentage(program_id, accounts, account)
304
+ MailerInstruction::Pause => {
305
+ process_pause(program_id, accounts)
306
+ }
307
+ MailerInstruction::Unpause => {
308
+ process_unpause(program_id, accounts)
428
309
  }
429
- MailerInstruction::Pause => process_pause(program_id, accounts),
430
- MailerInstruction::Unpause => process_unpause(program_id, accounts),
431
310
  MailerInstruction::DistributeClaimableFunds { recipient } => {
432
311
  process_distribute_claimable_funds(program_id, accounts, recipient)
433
312
  }
434
- MailerInstruction::ClaimExpiredShares { recipient } => {
435
- process_claim_expired_shares(program_id, accounts, recipient)
313
+ MailerInstruction::EmergencyUnpause => {
314
+ process_emergency_unpause(program_id, accounts)
436
315
  }
437
- MailerInstruction::EmergencyUnpause => process_emergency_unpause(program_id, accounts),
438
316
  }
439
317
  }
440
318
 
@@ -472,11 +350,7 @@ fn process_initialize(
472
350
  space as u64,
473
351
  program_id,
474
352
  ),
475
- &[
476
- owner.clone(),
477
- mailer_account.clone(),
478
- system_program.clone(),
479
- ],
353
+ &[owner.clone(), mailer_account.clone(), system_program.clone()],
480
354
  &[&[b"mailer", &[bump]]],
481
355
  )?;
482
356
 
@@ -524,30 +398,23 @@ fn process_send(
524
398
  }
525
399
 
526
400
  // Load mailer state
527
- let (mailer_pda, _) = assert_mailer_account(program_id, mailer_account)?;
528
401
  let mailer_data = mailer_account.try_borrow_data()?;
529
402
  let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
530
403
  drop(mailer_data);
531
404
 
532
- assert_token_program(token_program)?;
533
- assert_token_account(sender_usdc, sender.key, &mailer_state.usdc_mint)?;
534
- assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
535
-
536
405
  // Check if contract is paused
537
406
  if mailer_state.paused {
538
407
  return Err(MailerError::ContractPaused.into());
539
408
  }
540
409
 
541
- // Calculate effective fee based on custom discount (if any)
542
- let effective_fee =
543
- calculate_fee_with_discount(program_id, sender.key, accounts, mailer_state.send_fee)?;
544
-
545
410
  if revenue_share_to_receiver {
546
411
  // Priority mode: full fee with revenue sharing
547
412
 
548
413
  // Create or load recipient claim account
549
- let (claim_pda, claim_bump) =
550
- Pubkey::find_program_address(&[b"claim", to.as_ref()], program_id);
414
+ let (claim_pda, claim_bump) = Pubkey::find_program_address(
415
+ &[b"claim", to.as_ref()],
416
+ program_id
417
+ );
551
418
 
552
419
  if recipient_claim.key != &claim_pda {
553
420
  return Err(MailerError::InvalidPDA.into());
@@ -567,18 +434,13 @@ fn process_send(
567
434
  space as u64,
568
435
  program_id,
569
436
  ),
570
- &[
571
- sender.clone(),
572
- recipient_claim.clone(),
573
- system_program.clone(),
574
- ],
437
+ &[sender.clone(), recipient_claim.clone(), system_program.clone()],
575
438
  &[&[b"claim", to.as_ref(), &[claim_bump]]],
576
439
  )?;
577
440
 
578
441
  // Initialize claim account
579
442
  let mut claim_data = recipient_claim.try_borrow_mut_data()?;
580
- claim_data[0..8]
581
- .copy_from_slice(&hash_discriminator("account:RecipientClaim").to_le_bytes());
443
+ claim_data[0..8].copy_from_slice(&hash_discriminator("account:RecipientClaim").to_le_bytes());
582
444
 
583
445
  let claim_state = RecipientClaim {
584
446
  recipient: to,
@@ -591,53 +453,50 @@ fn process_send(
591
453
  drop(claim_data);
592
454
  }
593
455
 
594
- // Transfer effective fee (may be discounted)
595
- if effective_fee > 0 {
596
- invoke(
597
- &spl_token::instruction::transfer(
598
- token_program.key,
599
- sender_usdc.key,
600
- mailer_usdc.key,
601
- sender.key,
602
- &[],
603
- effective_fee,
604
- )?,
605
- &[
606
- sender_usdc.clone(),
607
- mailer_usdc.clone(),
608
- sender.clone(),
609
- token_program.clone(),
610
- ],
611
- )?;
456
+ // Transfer full send fee
457
+ invoke(
458
+ &spl_token::instruction::transfer(
459
+ token_program.key,
460
+ sender_usdc.key,
461
+ mailer_usdc.key,
462
+ sender.key,
463
+ &[],
464
+ mailer_state.send_fee,
465
+ )?,
466
+ &[
467
+ sender_usdc.clone(),
468
+ mailer_usdc.clone(),
469
+ sender.clone(),
470
+ token_program.clone(),
471
+ ],
472
+ )?;
612
473
 
613
- // Record revenue shares (only if fee > 0)
614
- record_shares(recipient_claim, mailer_account, to, effective_fee)?;
615
- }
474
+ // Record revenue shares
475
+ record_shares(recipient_claim, mailer_account, to, mailer_state.send_fee)?;
616
476
 
617
- msg!("Priority mail sent from {} to {}: {} (revenue share enabled, resolve sender: {}, effective fee: {})", sender.key, to, subject, _resolve_sender_to_name, effective_fee);
477
+ msg!("Priority mail sent from {} to {}: {} (revenue share enabled, resolve sender: {})", sender.key, to, subject, _resolve_sender_to_name);
618
478
  } else {
619
479
  // Standard mode: 10% fee only, no revenue sharing
620
- let owner_fee = (effective_fee * 10) / 100; // 10% of effective fee
480
+
481
+ let owner_fee = mailer_state.send_fee / 10; // 10% of send_fee
621
482
 
622
483
  // Transfer only owner fee (10%)
623
- if owner_fee > 0 {
624
- invoke(
625
- &spl_token::instruction::transfer(
626
- token_program.key,
627
- sender_usdc.key,
628
- mailer_usdc.key,
629
- sender.key,
630
- &[],
631
- owner_fee,
632
- )?,
633
- &[
634
- sender_usdc.clone(),
635
- mailer_usdc.clone(),
636
- sender.clone(),
637
- token_program.clone(),
638
- ],
639
- )?;
640
- }
484
+ invoke(
485
+ &spl_token::instruction::transfer(
486
+ token_program.key,
487
+ sender_usdc.key,
488
+ mailer_usdc.key,
489
+ sender.key,
490
+ &[],
491
+ owner_fee,
492
+ )?,
493
+ &[
494
+ sender_usdc.clone(),
495
+ mailer_usdc.clone(),
496
+ sender.clone(),
497
+ token_program.clone(),
498
+ ],
499
+ )?;
641
500
 
642
501
  // Update owner claimable
643
502
  let mut mailer_data = mailer_account.try_borrow_mut_data()?;
@@ -645,183 +504,81 @@ fn process_send(
645
504
  mailer_state.owner_claimable += owner_fee;
646
505
  mailer_state.serialize(&mut &mut mailer_data[8..])?;
647
506
 
648
- msg!(
649
- "Standard mail sent from {} to {}: {} (resolve sender: {}, effective fee: {})",
650
- sender.key,
651
- to,
652
- subject,
653
- _resolve_sender_to_name,
654
- effective_fee
655
- );
507
+ msg!("Standard mail sent from {} to {}: {} (resolve sender: {})", sender.key, to, subject, _resolve_sender_to_name);
656
508
  }
657
509
 
658
510
  Ok(())
659
511
  }
660
512
 
661
- /// Send prepared message with optional revenue sharing (references off-chain content via mailId)
662
- fn process_send_prepared(
663
- program_id: &Pubkey,
513
+ /// Process send to email address (no wallet known, only owner fee)
514
+ fn process_send_to_email(
515
+ _program_id: &Pubkey,
664
516
  accounts: &[AccountInfo],
665
- to: Pubkey,
666
- mail_id: String,
667
- revenue_share_to_receiver: bool,
668
- _resolve_sender_to_name: bool,
517
+ to_email: String,
518
+ subject: String,
519
+ _body: String,
669
520
  ) -> ProgramResult {
670
521
  let account_iter = &mut accounts.iter();
671
522
  let sender = next_account_info(account_iter)?;
672
- let recipient_claim = next_account_info(account_iter)?;
673
523
  let mailer_account = next_account_info(account_iter)?;
674
524
  let sender_usdc = next_account_info(account_iter)?;
675
525
  let mailer_usdc = next_account_info(account_iter)?;
676
526
  let token_program = next_account_info(account_iter)?;
677
- let system_program = next_account_info(account_iter)?;
678
527
 
679
528
  if !sender.is_signer {
680
529
  return Err(ProgramError::MissingRequiredSignature);
681
530
  }
682
531
 
683
532
  // Load mailer state
684
- let (mailer_pda, _) = assert_mailer_account(program_id, mailer_account)?;
685
533
  let mailer_data = mailer_account.try_borrow_data()?;
686
534
  let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
687
535
  drop(mailer_data);
688
536
 
689
- assert_token_program(token_program)?;
690
- assert_token_account(sender_usdc, sender.key, &mailer_state.usdc_mint)?;
691
- assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
692
-
693
537
  // Check if contract is paused
694
538
  if mailer_state.paused {
695
539
  return Err(MailerError::ContractPaused.into());
696
540
  }
697
541
 
698
- // Calculate effective fee based on custom discount (if any)
699
- let effective_fee =
700
- calculate_fee_with_discount(program_id, sender.key, accounts, mailer_state.send_fee)?;
701
-
702
- if revenue_share_to_receiver {
703
- // Priority mode: full fee with revenue sharing
704
-
705
- // Create or load recipient claim account
706
- let (claim_pda, claim_bump) =
707
- Pubkey::find_program_address(&[b"claim", to.as_ref()], program_id);
708
-
709
- if recipient_claim.key != &claim_pda {
710
- return Err(MailerError::InvalidPDA.into());
711
- }
712
-
713
- // Create claim account if needed
714
- if recipient_claim.lamports() == 0 {
715
- let rent = Rent::get()?;
716
- let space = 8 + RecipientClaim::LEN;
717
- let lamports = rent.minimum_balance(space);
718
-
719
- invoke_signed(
720
- &system_instruction::create_account(
721
- sender.key,
722
- recipient_claim.key,
723
- lamports,
724
- space as u64,
725
- program_id,
726
- ),
727
- &[
728
- sender.clone(),
729
- recipient_claim.clone(),
730
- system_program.clone(),
731
- ],
732
- &[&[b"claim", to.as_ref(), &[claim_bump]]],
733
- )?;
734
-
735
- // Initialize claim account
736
- let mut claim_data = recipient_claim.try_borrow_mut_data()?;
737
- claim_data[0..8]
738
- .copy_from_slice(&hash_discriminator("account:RecipientClaim").to_le_bytes());
739
-
740
- let claim_state = RecipientClaim {
741
- recipient: to,
742
- amount: 0,
743
- timestamp: 0,
744
- bump: claim_bump,
745
- };
746
-
747
- claim_state.serialize(&mut &mut claim_data[8..])?;
748
- drop(claim_data);
749
- }
750
-
751
- // Transfer effective fee (may be discounted)
752
- if effective_fee > 0 {
753
- invoke(
754
- &spl_token::instruction::transfer(
755
- token_program.key,
756
- sender_usdc.key,
757
- mailer_usdc.key,
758
- sender.key,
759
- &[],
760
- effective_fee,
761
- )?,
762
- &[
763
- sender_usdc.clone(),
764
- mailer_usdc.clone(),
765
- sender.clone(),
766
- token_program.clone(),
767
- ],
768
- )?;
769
-
770
- // Record revenue shares (only if fee > 0)
771
- record_shares(recipient_claim, mailer_account, to, effective_fee)?;
772
- }
542
+ // Calculate 10% owner fee (no revenue share since no wallet address)
543
+ let owner_fee = (mailer_state.send_fee * 10) / 100;
773
544
 
774
- msg!("Priority prepared mail sent from {} to {} (mailId: {}, revenue share enabled, resolve sender: {}, effective fee: {})", sender.key, to, mail_id, _resolve_sender_to_name, effective_fee);
775
- } else {
776
- // Standard mode: 10% fee only, no revenue sharing
777
- let owner_fee = (effective_fee * 10) / 100; // 10% of effective fee
545
+ // Transfer fee from sender to mailer
546
+ let transfer_ix = spl_token::instruction::transfer(
547
+ token_program.key,
548
+ sender_usdc.key,
549
+ mailer_usdc.key,
550
+ sender.key,
551
+ &[],
552
+ owner_fee,
553
+ )?;
778
554
 
779
- // Transfer only owner fee (10%)
780
- if owner_fee > 0 {
781
- invoke(
782
- &spl_token::instruction::transfer(
783
- token_program.key,
784
- sender_usdc.key,
785
- mailer_usdc.key,
786
- sender.key,
787
- &[],
788
- owner_fee,
789
- )?,
790
- &[
791
- sender_usdc.clone(),
792
- mailer_usdc.clone(),
793
- sender.clone(),
794
- token_program.clone(),
795
- ],
796
- )?;
797
- }
555
+ invoke(
556
+ &transfer_ix,
557
+ &[
558
+ sender_usdc.clone(),
559
+ mailer_usdc.clone(),
560
+ sender.clone(),
561
+ token_program.clone(),
562
+ ],
563
+ )?;
798
564
 
799
- // Update owner claimable
800
- let mut mailer_data = mailer_account.try_borrow_mut_data()?;
801
- let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
802
- mailer_state.owner_claimable += owner_fee;
803
- mailer_state.serialize(&mut &mut mailer_data[8..])?;
565
+ // Update owner claimable
566
+ let mut mailer_data = mailer_account.try_borrow_mut_data()?;
567
+ let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
568
+ mailer_state.owner_claimable += owner_fee;
569
+ mailer_state.serialize(&mut &mut mailer_data[8..])?;
804
570
 
805
- msg!(
806
- "Standard prepared mail sent from {} to {} (mailId: {}, resolve sender: {}, effective fee: {})",
807
- sender.key,
808
- to,
809
- mail_id,
810
- _resolve_sender_to_name,
811
- effective_fee
812
- );
813
- }
571
+ msg!("Mail sent from {} to email {}: {}", sender.key, to_email, subject);
814
572
 
815
573
  Ok(())
816
574
  }
817
575
 
818
- /// Process send to email address (no wallet known, only owner fee)
819
- fn process_send_to_email(
576
+ /// Process send prepared to email address (no wallet known, only owner fee)
577
+ fn process_send_prepared_to_email(
820
578
  _program_id: &Pubkey,
821
579
  accounts: &[AccountInfo],
822
580
  to_email: String,
823
- subject: String,
824
- _body: String,
581
+ mail_id: String,
825
582
  ) -> ProgramResult {
826
583
  let account_iter = &mut accounts.iter();
827
584
  let sender = next_account_info(account_iter)?;
@@ -835,127 +592,37 @@ fn process_send_to_email(
835
592
  }
836
593
 
837
594
  // Load mailer state
838
- let (mailer_pda, _) = assert_mailer_account(_program_id, mailer_account)?;
839
595
  let mailer_data = mailer_account.try_borrow_data()?;
840
596
  let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
841
597
  drop(mailer_data);
842
598
 
843
- assert_token_program(token_program)?;
844
- assert_token_account(sender_usdc, sender.key, &mailer_state.usdc_mint)?;
845
- assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
846
-
847
599
  // Check if contract is paused
848
600
  if mailer_state.paused {
849
601
  return Err(MailerError::ContractPaused.into());
850
602
  }
851
603
 
852
- // Calculate effective fee based on custom discount (if any)
853
- let effective_fee =
854
- calculate_fee_with_discount(_program_id, sender.key, accounts, mailer_state.send_fee)?;
855
-
856
604
  // Calculate 10% owner fee (no revenue share since no wallet address)
857
- let owner_fee = (effective_fee * 10) / 100;
605
+ let owner_fee = (mailer_state.send_fee * 10) / 100;
858
606
 
859
607
  // Transfer fee from sender to mailer
860
- if owner_fee > 0 {
861
- let transfer_ix = spl_token::instruction::transfer(
862
- token_program.key,
863
- sender_usdc.key,
864
- mailer_usdc.key,
865
- sender.key,
866
- &[],
867
- owner_fee,
868
- )?;
608
+ let transfer_ix = spl_token::instruction::transfer(
609
+ token_program.key,
610
+ sender_usdc.key,
611
+ mailer_usdc.key,
612
+ sender.key,
613
+ &[],
614
+ owner_fee,
615
+ )?;
869
616
 
870
- invoke(
871
- &transfer_ix,
872
- &[
873
- sender_usdc.clone(),
874
- mailer_usdc.clone(),
875
- sender.clone(),
876
- token_program.clone(),
877
- ],
878
- )?;
879
- }
880
-
881
- // Update owner claimable
882
- let mut mailer_data = mailer_account.try_borrow_mut_data()?;
883
- let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
884
- mailer_state.owner_claimable += owner_fee;
885
- mailer_state.serialize(&mut &mut mailer_data[8..])?;
886
-
887
- msg!(
888
- "Mail sent from {} to email {}: {} (effective fee: {})",
889
- sender.key,
890
- to_email,
891
- subject,
892
- effective_fee
893
- );
894
-
895
- Ok(())
896
- }
897
-
898
- /// Process send prepared to email address (no wallet known, only owner fee)
899
- fn process_send_prepared_to_email(
900
- _program_id: &Pubkey,
901
- accounts: &[AccountInfo],
902
- to_email: String,
903
- mail_id: String,
904
- ) -> ProgramResult {
905
- let account_iter = &mut accounts.iter();
906
- let sender = next_account_info(account_iter)?;
907
- let mailer_account = next_account_info(account_iter)?;
908
- let sender_usdc = next_account_info(account_iter)?;
909
- let mailer_usdc = next_account_info(account_iter)?;
910
- let token_program = next_account_info(account_iter)?;
911
-
912
- if !sender.is_signer {
913
- return Err(ProgramError::MissingRequiredSignature);
914
- }
915
-
916
- // Load mailer state
917
- let (mailer_pda, _) = assert_mailer_account(_program_id, mailer_account)?;
918
- let mailer_data = mailer_account.try_borrow_data()?;
919
- let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
920
- drop(mailer_data);
921
-
922
- assert_token_program(token_program)?;
923
- assert_token_account(sender_usdc, sender.key, &mailer_state.usdc_mint)?;
924
- assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
925
-
926
- // Check if contract is paused
927
- if mailer_state.paused {
928
- return Err(MailerError::ContractPaused.into());
929
- }
930
-
931
- // Calculate effective fee based on custom discount (if any)
932
- let effective_fee =
933
- calculate_fee_with_discount(_program_id, sender.key, accounts, mailer_state.send_fee)?;
934
-
935
- // Calculate 10% owner fee (no revenue share since no wallet address)
936
- let owner_fee = (effective_fee * 10) / 100;
937
-
938
- // Transfer fee from sender to mailer
939
- if owner_fee > 0 {
940
- let transfer_ix = spl_token::instruction::transfer(
941
- token_program.key,
942
- sender_usdc.key,
943
- mailer_usdc.key,
944
- sender.key,
945
- &[],
946
- owner_fee,
947
- )?;
948
-
949
- invoke(
950
- &transfer_ix,
951
- &[
952
- sender_usdc.clone(),
953
- mailer_usdc.clone(),
954
- sender.clone(),
955
- token_program.clone(),
956
- ],
957
- )?;
958
- }
617
+ invoke(
618
+ &transfer_ix,
619
+ &[
620
+ sender_usdc.clone(),
621
+ mailer_usdc.clone(),
622
+ sender.clone(),
623
+ token_program.clone(),
624
+ ],
625
+ )?;
959
626
 
960
627
  // Update owner claimable
961
628
  let mut mailer_data = mailer_account.try_borrow_mut_data()?;
@@ -963,170 +630,7 @@ fn process_send_prepared_to_email(
963
630
  mailer_state.owner_claimable += owner_fee;
964
631
  mailer_state.serialize(&mut &mut mailer_data[8..])?;
965
632
 
966
- msg!(
967
- "Prepared mail sent from {} to email {} (mailId: {}, effective fee: {})",
968
- sender.key,
969
- to_email,
970
- mail_id,
971
- effective_fee
972
- );
973
-
974
- Ok(())
975
- }
976
-
977
- /// Send message through webhook (references webhook by webhookId)
978
- fn process_send_through_webhook(
979
- program_id: &Pubkey,
980
- accounts: &[AccountInfo],
981
- to: Pubkey,
982
- webhook_id: String,
983
- revenue_share_to_receiver: bool,
984
- _resolve_sender_to_name: bool,
985
- ) -> ProgramResult {
986
- let account_iter = &mut accounts.iter();
987
- let sender = next_account_info(account_iter)?;
988
- let recipient_claim = next_account_info(account_iter)?;
989
- let mailer_account = next_account_info(account_iter)?;
990
- let sender_usdc = next_account_info(account_iter)?;
991
- let mailer_usdc = next_account_info(account_iter)?;
992
- let token_program = next_account_info(account_iter)?;
993
- let system_program = next_account_info(account_iter)?;
994
-
995
- if !sender.is_signer {
996
- return Err(ProgramError::MissingRequiredSignature);
997
- }
998
-
999
- // Load mailer state
1000
- let (mailer_pda, _) = assert_mailer_account(program_id, mailer_account)?;
1001
- let mailer_data = mailer_account.try_borrow_data()?;
1002
- let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1003
- drop(mailer_data);
1004
-
1005
- assert_token_program(token_program)?;
1006
- assert_token_account(sender_usdc, sender.key, &mailer_state.usdc_mint)?;
1007
- assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
1008
-
1009
- // Check if contract is paused
1010
- if mailer_state.paused {
1011
- return Err(MailerError::ContractPaused.into());
1012
- }
1013
-
1014
- // Calculate effective fee based on custom discount (if any)
1015
- let effective_fee =
1016
- calculate_fee_with_discount(program_id, sender.key, accounts, mailer_state.send_fee)?;
1017
-
1018
- if revenue_share_to_receiver {
1019
- // Priority mode: full fee with revenue sharing
1020
-
1021
- // Create or load recipient claim account
1022
- let (claim_pda, claim_bump) =
1023
- Pubkey::find_program_address(&[b"claim", to.as_ref()], program_id);
1024
-
1025
- if recipient_claim.key != &claim_pda {
1026
- return Err(MailerError::InvalidPDA.into());
1027
- }
1028
-
1029
- // Create claim account if needed
1030
- if recipient_claim.lamports() == 0 {
1031
- let rent = Rent::get()?;
1032
- let space = 8 + RecipientClaim::LEN;
1033
- let lamports = rent.minimum_balance(space);
1034
-
1035
- invoke_signed(
1036
- &system_instruction::create_account(
1037
- sender.key,
1038
- recipient_claim.key,
1039
- lamports,
1040
- space as u64,
1041
- program_id,
1042
- ),
1043
- &[
1044
- sender.clone(),
1045
- recipient_claim.clone(),
1046
- system_program.clone(),
1047
- ],
1048
- &[&[b"claim", to.as_ref(), &[claim_bump]]],
1049
- )?;
1050
-
1051
- // Initialize claim account
1052
- let mut claim_data = recipient_claim.try_borrow_mut_data()?;
1053
- claim_data[0..8]
1054
- .copy_from_slice(&hash_discriminator("account:RecipientClaim").to_le_bytes());
1055
-
1056
- let claim_state = RecipientClaim {
1057
- recipient: to,
1058
- amount: 0,
1059
- timestamp: 0,
1060
- bump: claim_bump,
1061
- };
1062
-
1063
- claim_state.serialize(&mut &mut claim_data[8..])?;
1064
- drop(claim_data);
1065
- }
1066
-
1067
- // Transfer effective fee (may be discounted)
1068
- if effective_fee > 0 {
1069
- invoke(
1070
- &spl_token::instruction::transfer(
1071
- token_program.key,
1072
- sender_usdc.key,
1073
- mailer_usdc.key,
1074
- sender.key,
1075
- &[],
1076
- effective_fee,
1077
- )?,
1078
- &[
1079
- sender_usdc.clone(),
1080
- mailer_usdc.clone(),
1081
- sender.clone(),
1082
- token_program.clone(),
1083
- ],
1084
- )?;
1085
-
1086
- // Record revenue shares (only if fee > 0)
1087
- record_shares(recipient_claim, mailer_account, to, effective_fee)?;
1088
- }
1089
-
1090
- msg!("Webhook mail sent from {} to {} (webhookId: {}, revenue share enabled, resolve sender: {}, effective fee: {})", sender.key, to, webhook_id, _resolve_sender_to_name, effective_fee);
1091
- } else {
1092
- // Standard mode: 10% fee only, no revenue sharing
1093
- let owner_fee = (effective_fee * 10) / 100; // 10% of effective fee
1094
-
1095
- // Transfer only owner fee (10%)
1096
- if owner_fee > 0 {
1097
- invoke(
1098
- &spl_token::instruction::transfer(
1099
- token_program.key,
1100
- sender_usdc.key,
1101
- mailer_usdc.key,
1102
- sender.key,
1103
- &[],
1104
- owner_fee,
1105
- )?,
1106
- &[
1107
- sender_usdc.clone(),
1108
- mailer_usdc.clone(),
1109
- sender.clone(),
1110
- token_program.clone(),
1111
- ],
1112
- )?;
1113
- }
1114
-
1115
- // Update owner claimable
1116
- let mut mailer_data = mailer_account.try_borrow_mut_data()?;
1117
- let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1118
- mailer_state.owner_claimable += owner_fee;
1119
- mailer_state.serialize(&mut &mut mailer_data[8..])?;
1120
-
1121
- msg!(
1122
- "Webhook mail sent from {} to {} (webhookId: {}, resolve sender: {}, effective fee: {})",
1123
- sender.key,
1124
- to,
1125
- webhook_id,
1126
- _resolve_sender_to_name,
1127
- effective_fee
1128
- );
1129
- }
633
+ msg!("Prepared mail sent from {} to email {} (mailId: {})", sender.key, to_email, mail_id);
1130
634
 
1131
635
  Ok(())
1132
636
  }
@@ -1145,13 +649,6 @@ fn process_claim_recipient_share(_program_id: &Pubkey, accounts: &[AccountInfo])
1145
649
  return Err(ProgramError::MissingRequiredSignature);
1146
650
  }
1147
651
 
1148
- let (mailer_pda, _) = assert_mailer_account(_program_id, mailer_account)?;
1149
- let (claim_pda, _) =
1150
- Pubkey::find_program_address(&[b"claim", recipient.key.as_ref()], _program_id);
1151
- if recipient_claim.key != &claim_pda {
1152
- return Err(MailerError::InvalidPDA.into());
1153
- }
1154
-
1155
652
  // Load claim state
1156
653
  let mut claim_data = recipient_claim.try_borrow_mut_data()?;
1157
654
  let mut claim_state: RecipientClaim = BorshDeserialize::deserialize(&mut &claim_data[8..])?;
@@ -1178,11 +675,6 @@ fn process_claim_recipient_share(_program_id: &Pubkey, accounts: &[AccountInfo])
1178
675
  // Load mailer state for PDA signing
1179
676
  let mailer_data = mailer_account.try_borrow_data()?;
1180
677
  let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1181
- drop(mailer_data);
1182
-
1183
- assert_token_program(token_program)?;
1184
- assert_token_account(recipient_usdc, recipient.key, &mailer_state.usdc_mint)?;
1185
- assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
1186
678
 
1187
679
  // Transfer USDC from mailer to recipient
1188
680
  invoke_signed(
@@ -1220,8 +712,6 @@ fn process_claim_owner_share(_program_id: &Pubkey, accounts: &[AccountInfo]) ->
1220
712
  return Err(ProgramError::MissingRequiredSignature);
1221
713
  }
1222
714
 
1223
- let (mailer_pda, _) = assert_mailer_account(_program_id, mailer_account)?;
1224
-
1225
715
  // Load and update mailer state
1226
716
  let mut mailer_data = mailer_account.try_borrow_mut_data()?;
1227
717
  let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
@@ -1239,10 +729,6 @@ fn process_claim_owner_share(_program_id: &Pubkey, accounts: &[AccountInfo]) ->
1239
729
  mailer_state.serialize(&mut &mut mailer_data[8..])?;
1240
730
  drop(mailer_data);
1241
731
 
1242
- assert_token_program(token_program)?;
1243
- assert_token_account(owner_usdc, owner.key, &mailer_state.usdc_mint)?;
1244
- assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
1245
-
1246
732
  // Transfer USDC from mailer to owner
1247
733
  invoke_signed(
1248
734
  &spl_token::instruction::transfer(
@@ -1276,8 +762,6 @@ fn process_set_fee(_program_id: &Pubkey, accounts: &[AccountInfo], new_fee: u64)
1276
762
  return Err(ProgramError::MissingRequiredSignature);
1277
763
  }
1278
764
 
1279
- assert_mailer_account(_program_id, mailer_account)?;
1280
-
1281
765
  // Load and update mailer state
1282
766
  let mut mailer_data = mailer_account.try_borrow_mut_data()?;
1283
767
  let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
@@ -1318,25 +802,21 @@ fn process_delegate_to(
1318
802
  return Err(ProgramError::MissingRequiredSignature);
1319
803
  }
1320
804
 
1321
- let (mailer_pda, _) = assert_mailer_account(program_id, mailer_account)?;
1322
-
1323
805
  // Load mailer state
1324
806
  let mailer_data = mailer_account.try_borrow_data()?;
1325
807
  let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1326
808
  drop(mailer_data);
1327
809
 
1328
- assert_token_program(token_program)?;
1329
- assert_token_account(delegator_usdc, delegator.key, &mailer_state.usdc_mint)?;
1330
- assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
1331
-
1332
810
  // Check if contract is paused
1333
811
  if mailer_state.paused {
1334
812
  return Err(MailerError::ContractPaused.into());
1335
813
  }
1336
814
 
1337
815
  // Verify delegation account PDA
1338
- let (delegation_pda, delegation_bump) =
1339
- Pubkey::find_program_address(&[b"delegation", delegator.key.as_ref()], program_id);
816
+ let (delegation_pda, delegation_bump) = Pubkey::find_program_address(
817
+ &[b"delegation", delegator.key.as_ref()],
818
+ program_id
819
+ );
1340
820
 
1341
821
  if delegation_account.key != &delegation_pda {
1342
822
  return Err(MailerError::InvalidPDA.into());
@@ -1366,8 +846,7 @@ fn process_delegate_to(
1366
846
 
1367
847
  // Initialize delegation account
1368
848
  let mut delegation_data = delegation_account.try_borrow_mut_data()?;
1369
- delegation_data[0..8]
1370
- .copy_from_slice(&hash_discriminator("account:Delegation").to_le_bytes());
849
+ delegation_data[0..8].copy_from_slice(&hash_discriminator("account:Delegation").to_le_bytes());
1371
850
 
1372
851
  let delegation_state = Delegation {
1373
852
  delegator: *delegator.key,
@@ -1403,8 +882,7 @@ fn process_delegate_to(
1403
882
 
1404
883
  // Update delegation
1405
884
  let mut delegation_data = delegation_account.try_borrow_mut_data()?;
1406
- let mut delegation_state: Delegation =
1407
- BorshDeserialize::deserialize(&mut &delegation_data[8..])?;
885
+ let mut delegation_state: Delegation = BorshDeserialize::deserialize(&mut &delegation_data[8..])?;
1408
886
  delegation_state.delegate = delegate;
1409
887
  delegation_state.serialize(&mut &mut delegation_data[8..])?;
1410
888
 
@@ -1413,31 +891,18 @@ fn process_delegate_to(
1413
891
  }
1414
892
 
1415
893
  /// Reject delegation
1416
- fn process_reject_delegation(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
894
+ fn process_reject_delegation(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
1417
895
  let account_iter = &mut accounts.iter();
1418
896
  let rejector = next_account_info(account_iter)?;
1419
897
  let delegation_account = next_account_info(account_iter)?;
1420
- let mailer_account = next_account_info(account_iter)?;
1421
898
 
1422
899
  if !rejector.is_signer {
1423
900
  return Err(ProgramError::MissingRequiredSignature);
1424
901
  }
1425
902
 
1426
- // Verify mailer state PDA and ensure contract is not paused
1427
- let (_mailer_pda, _) = assert_mailer_account(program_id, mailer_account)?;
1428
-
1429
- let mailer_data = mailer_account.try_borrow_data()?;
1430
- let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1431
- drop(mailer_data);
1432
-
1433
- if mailer_state.paused {
1434
- return Err(MailerError::ContractPaused.into());
1435
- }
1436
-
1437
903
  // Load and update delegation state
1438
904
  let mut delegation_data = delegation_account.try_borrow_mut_data()?;
1439
- let mut delegation_state: Delegation =
1440
- BorshDeserialize::deserialize(&mut &delegation_data[8..])?;
905
+ let mut delegation_state: Delegation = BorshDeserialize::deserialize(&mut &delegation_data[8..])?;
1441
906
 
1442
907
  // Verify the rejector is the current delegate
1443
908
  if delegation_state.delegate != Some(*rejector.key) {
@@ -1465,8 +930,6 @@ fn process_set_delegation_fee(
1465
930
  return Err(ProgramError::MissingRequiredSignature);
1466
931
  }
1467
932
 
1468
- assert_mailer_account(_program_id, mailer_account)?;
1469
-
1470
933
  // Load and update mailer state
1471
934
  let mut mailer_data = mailer_account.try_borrow_mut_data()?;
1472
935
  let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
@@ -1488,194 +951,6 @@ fn process_set_delegation_fee(
1488
951
  Ok(())
1489
952
  }
1490
953
 
1491
- /// Set custom fee percentage for a specific address (owner only)
1492
- fn process_set_custom_fee_percentage(
1493
- program_id: &Pubkey,
1494
- accounts: &[AccountInfo],
1495
- account: Pubkey,
1496
- percentage: u8,
1497
- ) -> ProgramResult {
1498
- let account_iter = &mut accounts.iter();
1499
- let owner = next_account_info(account_iter)?;
1500
- let mailer_account = next_account_info(account_iter)?;
1501
- let fee_discount_account = next_account_info(account_iter)?;
1502
- let _target_account = next_account_info(account_iter)?;
1503
- let payer = next_account_info(account_iter)?;
1504
- let system_program = next_account_info(account_iter)?;
1505
-
1506
- if !owner.is_signer {
1507
- return Err(ProgramError::MissingRequiredSignature);
1508
- }
1509
-
1510
- assert_mailer_account(program_id, mailer_account)?;
1511
-
1512
- // Load mailer state and verify owner
1513
- let mailer_data = mailer_account.try_borrow_data()?;
1514
- let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1515
- drop(mailer_data);
1516
-
1517
- if mailer_state.owner != *owner.key {
1518
- return Err(MailerError::OnlyOwner.into());
1519
- }
1520
-
1521
- // Check if contract is paused
1522
- if mailer_state.paused {
1523
- return Err(MailerError::ContractPaused.into());
1524
- }
1525
-
1526
- // Validate percentage
1527
- if percentage > 100 {
1528
- return Err(MailerError::InvalidPercentage.into());
1529
- }
1530
-
1531
- // Verify fee discount account PDA
1532
- let (discount_pda, bump) =
1533
- Pubkey::find_program_address(&[b"discount", account.as_ref()], program_id);
1534
-
1535
- if fee_discount_account.key != &discount_pda {
1536
- return Err(MailerError::InvalidPDA.into());
1537
- }
1538
-
1539
- // Create or update fee discount account
1540
- if fee_discount_account.lamports() == 0 {
1541
- let rent = Rent::get()?;
1542
- let space = 8 + FeeDiscount::LEN;
1543
- let lamports = rent.minimum_balance(space);
1544
-
1545
- invoke_signed(
1546
- &system_instruction::create_account(
1547
- payer.key,
1548
- fee_discount_account.key,
1549
- lamports,
1550
- space as u64,
1551
- program_id,
1552
- ),
1553
- &[
1554
- payer.clone(),
1555
- fee_discount_account.clone(),
1556
- system_program.clone(),
1557
- ],
1558
- &[&[b"discount", account.as_ref(), &[bump]]],
1559
- )?;
1560
-
1561
- // Initialize discount account
1562
- let mut discount_data = fee_discount_account.try_borrow_mut_data()?;
1563
- discount_data[0..8]
1564
- .copy_from_slice(&hash_discriminator("account:FeeDiscount").to_le_bytes());
1565
-
1566
- let fee_discount = FeeDiscount {
1567
- account,
1568
- discount: 100 - percentage, // Store as discount: 0% fee = 100 discount, 100% fee = 0 discount
1569
- bump,
1570
- };
1571
-
1572
- fee_discount.serialize(&mut &mut discount_data[8..])?;
1573
- } else {
1574
- // Update existing discount account
1575
- let mut discount_data = fee_discount_account.try_borrow_mut_data()?;
1576
- let mut fee_discount: FeeDiscount =
1577
- BorshDeserialize::deserialize(&mut &discount_data[8..])?;
1578
- fee_discount.discount = 100 - percentage; // Store as discount
1579
- fee_discount.serialize(&mut &mut discount_data[8..])?;
1580
- }
1581
-
1582
- msg!("Custom fee percentage set for {}: {}%", account, percentage);
1583
- Ok(())
1584
- }
1585
-
1586
- /// Clear custom fee percentage for a specific address (owner only)
1587
- fn process_clear_custom_fee_percentage(
1588
- program_id: &Pubkey,
1589
- accounts: &[AccountInfo],
1590
- account: Pubkey,
1591
- ) -> ProgramResult {
1592
- let account_iter = &mut accounts.iter();
1593
- let owner = next_account_info(account_iter)?;
1594
- let mailer_account = next_account_info(account_iter)?;
1595
- let fee_discount_account = next_account_info(account_iter)?;
1596
-
1597
- if !owner.is_signer {
1598
- return Err(ProgramError::MissingRequiredSignature);
1599
- }
1600
-
1601
- assert_mailer_account(program_id, mailer_account)?;
1602
-
1603
- // Load mailer state and verify owner
1604
- let mailer_data = mailer_account.try_borrow_data()?;
1605
- let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1606
- drop(mailer_data);
1607
-
1608
- if mailer_state.owner != *owner.key {
1609
- return Err(MailerError::OnlyOwner.into());
1610
- }
1611
-
1612
- // Check if contract is paused
1613
- if mailer_state.paused {
1614
- return Err(MailerError::ContractPaused.into());
1615
- }
1616
-
1617
- // Verify fee discount account PDA
1618
- let (discount_pda, _) =
1619
- Pubkey::find_program_address(&[b"discount", account.as_ref()], program_id);
1620
-
1621
- if fee_discount_account.key != &discount_pda {
1622
- return Err(MailerError::InvalidPDA.into());
1623
- }
1624
-
1625
- // Clear by setting discount to 0 (no discount = 100% fee = default behavior)
1626
- if fee_discount_account.lamports() > 0 {
1627
- let mut discount_data = fee_discount_account.try_borrow_mut_data()?;
1628
- let mut fee_discount: FeeDiscount =
1629
- BorshDeserialize::deserialize(&mut &discount_data[8..])?;
1630
- fee_discount.discount = 0; // 0 discount = 100% fee = default
1631
- fee_discount.serialize(&mut &mut discount_data[8..])?;
1632
- }
1633
-
1634
- msg!(
1635
- "Custom fee percentage cleared for {} (reset to 100%)",
1636
- account
1637
- );
1638
- Ok(())
1639
- }
1640
-
1641
- fn assert_token_program(token_program: &AccountInfo) -> Result<(), ProgramError> {
1642
- if token_program.key != &spl_token::id() {
1643
- return Err(MailerError::InvalidTokenProgram.into());
1644
- }
1645
- Ok(())
1646
- }
1647
-
1648
- fn assert_token_account(
1649
- token_account_info: &AccountInfo,
1650
- expected_owner: &Pubkey,
1651
- expected_mint: &Pubkey,
1652
- ) -> Result<(), ProgramError> {
1653
- let data = token_account_info.try_borrow_data()?;
1654
- let token_account = TokenAccount::unpack(&data)?;
1655
- drop(data);
1656
-
1657
- if token_account.owner != *expected_owner {
1658
- return Err(MailerError::InvalidAccountOwner.into());
1659
- }
1660
-
1661
- if token_account.mint != *expected_mint {
1662
- return Err(MailerError::InvalidMint.into());
1663
- }
1664
-
1665
- Ok(())
1666
- }
1667
-
1668
- fn assert_mailer_account(
1669
- program_id: &Pubkey,
1670
- mailer_account: &AccountInfo,
1671
- ) -> Result<(Pubkey, u8), ProgramError> {
1672
- let (mailer_pda, bump) = Pubkey::find_program_address(&[b"mailer"], program_id);
1673
- if mailer_account.key != &mailer_pda {
1674
- return Err(MailerError::InvalidPDA.into());
1675
- }
1676
- Ok((mailer_pda, bump))
1677
- }
1678
-
1679
954
  /// Record revenue shares for priority messages
1680
955
  fn record_shares(
1681
956
  recipient_claim: &AccountInfo,
@@ -1702,51 +977,10 @@ fn record_shares(
1702
977
  mailer_state.owner_claimable += owner_amount;
1703
978
  mailer_state.serialize(&mut &mut mailer_data[8..])?;
1704
979
 
1705
- msg!(
1706
- "Shares recorded: recipient {}, owner {}",
1707
- recipient_amount,
1708
- owner_amount
1709
- );
980
+ msg!("Shares recorded: recipient {}, owner {}", recipient_amount, owner_amount);
1710
981
  Ok(())
1711
982
  }
1712
983
 
1713
- /// Calculate the effective fee for an account based on custom discount
1714
- /// If no discount account exists, returns full base_fee (default behavior)
1715
- /// Otherwise applies discount: fee = base_fee * (100 - discount) / 100
1716
- fn calculate_fee_with_discount(
1717
- program_id: &Pubkey,
1718
- account: &Pubkey,
1719
- accounts: &[AccountInfo],
1720
- base_fee: u64,
1721
- ) -> Result<u64, ProgramError> {
1722
- // Try to find fee discount account
1723
- let (discount_pda, _) =
1724
- Pubkey::find_program_address(&[b"discount", account.as_ref()], program_id);
1725
-
1726
- // Check if any account in the accounts slice matches the discount PDA
1727
- let discount_account = accounts.iter().find(|acc| acc.key == &discount_pda);
1728
-
1729
- if let Some(discount_acc) = discount_account {
1730
- // Account exists and has lamports - load the discount
1731
- if discount_acc.lamports() > 0 {
1732
- let discount_data = discount_acc.try_borrow_data()?;
1733
- if discount_data.len() >= 8 + FeeDiscount::LEN {
1734
- let fee_discount: FeeDiscount =
1735
- BorshDeserialize::deserialize(&mut &discount_data[8..])?;
1736
- let discount = fee_discount.discount as u64;
1737
-
1738
- // Apply discount: fee = base_fee * (100 - discount) / 100
1739
- // Examples: discount=0 → 100% fee, discount=50 → 50% fee, discount=100 → 0% fee (free)
1740
- let effective_fee = (base_fee * (100 - discount)) / 100;
1741
- return Ok(effective_fee);
1742
- }
1743
- }
1744
- }
1745
-
1746
- // No discount account or uninitialized - use full fee (default behavior)
1747
- Ok(base_fee)
1748
- }
1749
-
1750
984
  /// Pause the contract and distribute owner claimable funds
1751
985
  fn process_pause(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
1752
986
  let account_iter = &mut accounts.iter();
@@ -1760,8 +994,6 @@ fn process_pause(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResul
1760
994
  return Err(ProgramError::MissingRequiredSignature);
1761
995
  }
1762
996
 
1763
- let (mailer_pda, _) = assert_mailer_account(_program_id, mailer_account)?;
1764
-
1765
997
  // Load and update mailer state
1766
998
  let mut mailer_data = mailer_account.try_borrow_mut_data()?;
1767
999
  let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
@@ -1779,17 +1011,13 @@ fn process_pause(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResul
1779
1011
  // Set paused state
1780
1012
  mailer_state.paused = true;
1781
1013
 
1782
- assert_token_program(token_program)?;
1783
-
1784
1014
  // Distribute owner claimable funds if any
1785
1015
  if mailer_state.owner_claimable > 0 {
1786
1016
  let amount = mailer_state.owner_claimable;
1787
1017
  mailer_state.owner_claimable = 0;
1788
1018
 
1789
- assert_token_account(owner_usdc, owner.key, &mailer_state.usdc_mint)?;
1790
- assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
1791
-
1792
1019
  // Transfer USDC from mailer to owner
1020
+ let (mailer_pda, bump) = Pubkey::find_program_address(&[b"mailer"], _program_id);
1793
1021
  invoke_signed(
1794
1022
  &spl_token::instruction::transfer(
1795
1023
  token_program.key,
@@ -1799,13 +1027,8 @@ fn process_pause(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResul
1799
1027
  &[],
1800
1028
  amount,
1801
1029
  )?,
1802
- &[
1803
- mailer_usdc.clone(),
1804
- owner_usdc.clone(),
1805
- mailer_account.clone(),
1806
- token_program.clone(),
1807
- ],
1808
- &[&[b"mailer", &[mailer_state.bump]]],
1030
+ &[mailer_usdc.clone(), owner_usdc.clone(), token_program.clone()],
1031
+ &[&[b"mailer", &[bump]]],
1809
1032
  )?;
1810
1033
 
1811
1034
  msg!("Distributed owner funds during pause: {}", amount);
@@ -1813,7 +1036,7 @@ fn process_pause(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResul
1813
1036
 
1814
1037
  // Save updated state
1815
1038
  mailer_state.serialize(&mut &mut mailer_data[8..])?;
1816
-
1039
+
1817
1040
  msg!("Contract paused by owner: {}", owner.key);
1818
1041
  Ok(())
1819
1042
  }
@@ -1828,8 +1051,6 @@ fn process_unpause(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramRes
1828
1051
  return Err(ProgramError::MissingRequiredSignature);
1829
1052
  }
1830
1053
 
1831
- assert_mailer_account(_program_id, mailer_account)?;
1832
-
1833
1054
  // Load and update mailer state
1834
1055
  let mut mailer_data = mailer_account.try_borrow_mut_data()?;
1835
1056
  let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
@@ -1847,11 +1068,43 @@ fn process_unpause(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramRes
1847
1068
  // Set unpaused state
1848
1069
  mailer_state.paused = false;
1849
1070
  mailer_state.serialize(&mut &mut mailer_data[8..])?;
1850
-
1071
+
1851
1072
  msg!("Contract unpaused by owner: {}", owner.key);
1852
1073
  Ok(())
1853
1074
  }
1854
1075
 
1076
+ /// Emergency unpause without fund distribution (owner only)
1077
+ fn process_emergency_unpause(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
1078
+ let account_iter = &mut accounts.iter();
1079
+ let owner = next_account_info(account_iter)?;
1080
+ let mailer_account = next_account_info(account_iter)?;
1081
+
1082
+ if !owner.is_signer {
1083
+ return Err(ProgramError::MissingRequiredSignature);
1084
+ }
1085
+
1086
+ // Load and update mailer state
1087
+ let mut mailer_data = mailer_account.try_borrow_mut_data()?;
1088
+ let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1089
+
1090
+ // Verify owner
1091
+ if mailer_state.owner != *owner.key {
1092
+ return Err(MailerError::OnlyOwner.into());
1093
+ }
1094
+
1095
+ // Check if not paused
1096
+ if !mailer_state.paused {
1097
+ return Err(MailerError::ContractNotPaused.into());
1098
+ }
1099
+
1100
+ // Set unpaused state without fund distribution
1101
+ mailer_state.paused = false;
1102
+ mailer_state.serialize(&mut &mut mailer_data[8..])?;
1103
+
1104
+ msg!("Contract emergency unpaused by owner: {} - funds can be claimed manually", owner.key);
1105
+ Ok(())
1106
+ }
1107
+
1855
1108
  /// Distribute claimable funds when contract is paused
1856
1109
  fn process_distribute_claimable_funds(
1857
1110
  _program_id: &Pubkey,
@@ -1866,8 +1119,6 @@ fn process_distribute_claimable_funds(
1866
1119
  let mailer_usdc = next_account_info(account_iter)?;
1867
1120
  let token_program = next_account_info(account_iter)?;
1868
1121
 
1869
- let (mailer_pda, _) = assert_mailer_account(_program_id, mailer_account)?;
1870
-
1871
1122
  // Load mailer state to check if paused
1872
1123
  let mailer_data = mailer_account.try_borrow_data()?;
1873
1124
  let mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
@@ -1884,8 +1135,6 @@ fn process_distribute_claimable_funds(
1884
1135
  return Err(MailerError::InvalidPDA.into());
1885
1136
  }
1886
1137
 
1887
- assert_token_program(token_program)?;
1888
-
1889
1138
  // Load and update recipient claim
1890
1139
  let mut claim_data = recipient_claim_account.try_borrow_mut_data()?;
1891
1140
  let mut claim_state: RecipientClaim = BorshDeserialize::deserialize(&mut &claim_data[8..])?;
@@ -1898,10 +1147,8 @@ fn process_distribute_claimable_funds(
1898
1147
  claim_state.amount = 0;
1899
1148
  claim_state.timestamp = 0;
1900
1149
 
1901
- assert_token_account(recipient_usdc, &recipient, &mailer_state.usdc_mint)?;
1902
- assert_token_account(mailer_usdc, &mailer_pda, &mailer_state.usdc_mint)?;
1903
-
1904
1150
  // Transfer USDC from mailer to recipient
1151
+ let (mailer_pda, bump) = Pubkey::find_program_address(&[b"mailer"], _program_id);
1905
1152
  invoke_signed(
1906
1153
  &spl_token::instruction::transfer(
1907
1154
  token_program.key,
@@ -1911,118 +1158,16 @@ fn process_distribute_claimable_funds(
1911
1158
  &[],
1912
1159
  amount,
1913
1160
  )?,
1914
- &[
1915
- mailer_usdc.clone(),
1916
- recipient_usdc.clone(),
1917
- mailer_account.clone(),
1918
- token_program.clone(),
1919
- ],
1920
- &[&[b"mailer", &[mailer_state.bump]]],
1161
+ &[mailer_usdc.clone(), recipient_usdc.clone(), token_program.clone()],
1162
+ &[&[b"mailer", &[bump]]],
1921
1163
  )?;
1922
1164
 
1923
1165
  claim_state.serialize(&mut &mut claim_data[8..])?;
1924
-
1166
+
1925
1167
  msg!("Distributed claimable funds to {}: {}", recipient, amount);
1926
1168
  Ok(())
1927
1169
  }
1928
1170
 
1929
- /// Claim expired shares and move them under owner control (owner only)
1930
- fn process_claim_expired_shares(
1931
- program_id: &Pubkey,
1932
- accounts: &[AccountInfo],
1933
- recipient: Pubkey,
1934
- ) -> ProgramResult {
1935
- let account_iter = &mut accounts.iter();
1936
- let owner = next_account_info(account_iter)?;
1937
- let mailer_account = next_account_info(account_iter)?;
1938
- let recipient_claim_account = next_account_info(account_iter)?;
1939
-
1940
- if !owner.is_signer {
1941
- return Err(ProgramError::MissingRequiredSignature);
1942
- }
1943
-
1944
- let (_mailer_pda, _) = assert_mailer_account(program_id, mailer_account)?;
1945
-
1946
- // Load and verify mailer state
1947
- let mut mailer_data = mailer_account.try_borrow_mut_data()?;
1948
- let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
1949
-
1950
- if mailer_state.owner != *owner.key {
1951
- return Err(MailerError::OnlyOwner.into());
1952
- }
1953
-
1954
- // Verify recipient claim PDA
1955
- let (claim_pda, _) = Pubkey::find_program_address(&[b"claim", recipient.as_ref()], program_id);
1956
- if recipient_claim_account.key != &claim_pda {
1957
- return Err(MailerError::InvalidPDA.into());
1958
- }
1959
-
1960
- // Load and validate claim state
1961
- let mut claim_data = recipient_claim_account.try_borrow_mut_data()?;
1962
- let mut claim_state: RecipientClaim = BorshDeserialize::deserialize(&mut &claim_data[8..])?;
1963
-
1964
- if claim_state.recipient != recipient {
1965
- return Err(MailerError::InvalidRecipient.into());
1966
- }
1967
- if claim_state.amount == 0 {
1968
- return Err(MailerError::NoClaimableAmount.into());
1969
- }
1970
-
1971
- let current_time = Clock::get()?.unix_timestamp;
1972
- if current_time <= claim_state.timestamp + CLAIM_PERIOD {
1973
- return Err(MailerError::ClaimPeriodNotExpired.into());
1974
- }
1975
-
1976
- let amount = claim_state.amount;
1977
- claim_state.amount = 0;
1978
- claim_state.timestamp = 0;
1979
- claim_state.serialize(&mut &mut claim_data[8..])?;
1980
- drop(claim_data);
1981
-
1982
- mailer_state.owner_claimable += amount;
1983
- mailer_state.serialize(&mut &mut mailer_data[8..])?;
1984
-
1985
- msg!("Expired shares claimed for {}: {}", recipient, amount);
1986
- Ok(())
1987
- }
1988
-
1989
- /// Emergency unpause without fund distribution (owner only)
1990
- fn process_emergency_unpause(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
1991
- let account_iter = &mut accounts.iter();
1992
- let owner = next_account_info(account_iter)?;
1993
- let mailer_account = next_account_info(account_iter)?;
1994
-
1995
- if !owner.is_signer {
1996
- return Err(ProgramError::MissingRequiredSignature);
1997
- }
1998
-
1999
- assert_mailer_account(_program_id, mailer_account)?;
2000
-
2001
- // Load and update mailer state
2002
- let mut mailer_data = mailer_account.try_borrow_mut_data()?;
2003
- let mut mailer_state: MailerState = BorshDeserialize::deserialize(&mut &mailer_data[8..])?;
2004
-
2005
- // Verify owner
2006
- if mailer_state.owner != *owner.key {
2007
- return Err(MailerError::OnlyOwner.into());
2008
- }
2009
-
2010
- // Check if not paused
2011
- if !mailer_state.paused {
2012
- return Err(MailerError::ContractNotPaused.into());
2013
- }
2014
-
2015
- // Set unpaused state without fund distribution
2016
- mailer_state.paused = false;
2017
- mailer_state.serialize(&mut &mut mailer_data[8..])?;
2018
-
2019
- msg!(
2020
- "Contract emergency unpaused by owner: {} - funds can be claimed manually",
2021
- owner.key
2022
- );
2023
- Ok(())
2024
- }
2025
-
2026
1171
  /// Simple hash function for account discriminators
2027
1172
  fn hash_discriminator(name: &str) -> u64 {
2028
1173
  use std::collections::hash_map::DefaultHasher;