Skip to main content

solmail_program/instructions/escrow/
create.rs

1use pinocchio::{
2    account_info::AccountInfo,
3    instruction::Signer,
4    program_error::ProgramError,
5    pubkey::{find_program_address, Pubkey},
6    seeds,
7    sysvars::{clock::Clock, rent::Rent, Sysvar},
8    ProgramResult,
9};
10use pinocchio_system::instructions::CreateAccount;
11
12use crate::{
13    constants::{DEFAULT_TIMEOUT_SECONDS, ESCROW_SEED, ESCROW_VAULT_SEED, MIN_ESCROW_LAMPORTS},
14    error::SolMailError,
15    state::escrow::{EscrowOrder, ESCROW_ORDER_SIZE},
16};
17
18/// Create a new escrow order
19///
20/// Accounts:
21/// 0. `[signer, writable]` Payer - user creating the escrow
22/// 1. `[writable]` Escrow state account (PDA)
23/// 2. `[writable]` Escrow vault account (PDA) - holds the SOL
24/// 3. `[]` System program
25///
26/// Data:
27/// - amount: u64 (8 bytes)
28/// - mail_id_low: u64 (8 bytes)
29/// - mail_id_high: u64 (8 bytes)
30/// - timeout_days: u8 (1 byte) - 0 means default 7 days
31pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
32    // Parse instruction data
33    if data.len() < 25 {
34        return Err(ProgramError::InvalidInstructionData);
35    }
36
37    let amount = u64::from_le_bytes(data[0..8].try_into().unwrap());
38    let mail_id_low = u64::from_le_bytes(data[8..16].try_into().unwrap());
39    let mail_id_high = u64::from_le_bytes(data[16..24].try_into().unwrap());
40    let timeout_days = data[24];
41
42    // Validate amount
43    if amount < MIN_ESCROW_LAMPORTS {
44        return Err(SolMailError::AmountBelowMinimum.into());
45    }
46
47    // Parse accounts
48    let [payer, escrow_state, escrow_vault, _system_program] = accounts else {
49        return Err(ProgramError::NotEnoughAccountKeys);
50    };
51
52    // Verify payer is signer
53    if !payer.is_signer() {
54        return Err(SolMailError::MissingRequiredSignature.into());
55    }
56
57    // Build mail_id bytes for PDA derivation
58    let mut mail_id_bytes = [0u8; 16];
59    mail_id_bytes[..8].copy_from_slice(&mail_id_low.to_le_bytes());
60    mail_id_bytes[8..].copy_from_slice(&mail_id_high.to_le_bytes());
61
62    // Derive escrow state PDA
63    let (escrow_pda, escrow_bump) =
64        find_program_address(&[ESCROW_SEED, &mail_id_bytes], program_id);
65
66    if escrow_state.key() != &escrow_pda {
67        return Err(SolMailError::InvalidPda.into());
68    }
69
70    // Derive escrow vault PDA
71    let (vault_pda, vault_bump) =
72        find_program_address(&[ESCROW_VAULT_SEED, &mail_id_bytes], program_id);
73
74    if escrow_vault.key() != &vault_pda {
75        return Err(SolMailError::InvalidPda.into());
76    }
77
78    // Get current timestamp
79    let clock = Clock::get()?;
80    let created_at = clock.unix_timestamp;
81
82    // Calculate timeout
83    let timeout_seconds = if timeout_days == 0 {
84        DEFAULT_TIMEOUT_SECONDS
85    } else {
86        (timeout_days as i64) * 24 * 60 * 60
87    };
88    let timeout_at = created_at + timeout_seconds;
89
90    // Calculate rent for escrow state account
91    let rent = Rent::get()?;
92    let rent_lamports = rent.minimum_balance(ESCROW_ORDER_SIZE);
93
94    // Build signer for escrow state PDA
95    let escrow_bump_ref = &[escrow_bump];
96    let escrow_seeds = seeds!(ESCROW_SEED, &mail_id_bytes, escrow_bump_ref);
97    let escrow_signer = Signer::from(&escrow_seeds);
98
99    // Create escrow state account
100    CreateAccount {
101        from: payer,
102        to: escrow_state,
103        lamports: rent_lamports,
104        space: ESCROW_ORDER_SIZE as u64,
105        owner: program_id,
106    }
107    .invoke_signed(&[escrow_signer])?;
108
109    // Build signer for vault PDA
110    let vault_bump_ref = &[vault_bump];
111    let vault_seeds = seeds!(ESCROW_VAULT_SEED, &mail_id_bytes, vault_bump_ref);
112    let vault_signer = Signer::from(&vault_seeds);
113
114    // Create escrow vault account (holds the SOL)
115    CreateAccount {
116        from: payer,
117        to: escrow_vault,
118        lamports: amount,
119        space: 0,
120        owner: program_id,
121    }
122    .invoke_signed(&[vault_signer])?;
123
124    // Initialize escrow state data
125    let escrow_data = unsafe { escrow_state.borrow_mut_data_unchecked() };
126    let escrow = EscrowOrder::from_bytes_mut(escrow_data)?;
127
128    escrow.payer.copy_from_slice(payer.key().as_ref());
129    escrow.amount = amount;
130    escrow.mail_id = [mail_id_low, mail_id_high];
131    escrow.created_at = created_at;
132    escrow.timeout_at = timeout_at;
133    escrow.is_usdc = false;
134    escrow.delivered = false;
135    escrow.bump = escrow_bump;
136
137    Ok(())
138}