Skip to main content

winterwallet_client/
plan.rs

1use solana_address::Address;
2use solana_instruction::{AccountMeta, Instruction};
3use winterwallet_common::SIGNATURE_LEN;
4
5use crate::{
6    AdvancePayload, Error, advance, advance_preimage, close, encode_advance,
7    transaction::{
8        DEFAULT_ADVANCE_COMPUTE_UNIT_LIMIT, estimate_legacy_transaction_size,
9        validate_legacy_transaction_size, with_compute_budget,
10    },
11    withdraw,
12};
13
14/// Fully assembled but unsigned Advance instruction plan.
15///
16/// A plan owns the encoded CPI payload and passthrough account order, so callers
17/// cannot accidentally sign one account order and submit another.
18pub struct AdvancePlan {
19    wallet_pda: Address,
20    new_root: [u8; 32],
21    payload: AdvancePayload,
22    account_addresses: Vec<[u8; 32]>,
23}
24
25impl AdvancePlan {
26    /// Build an Advance plan from inner CPI instructions.
27    pub fn new(
28        wallet_pda: &Address,
29        new_root: &[u8; 32],
30        inner_instructions: &[Instruction],
31    ) -> Result<Self, Error> {
32        let payload = encode_advance(inner_instructions)?;
33        let account_addresses = payload
34            .accounts
35            .iter()
36            .map(|meta| *meta.pubkey.as_array())
37            .collect();
38
39        Ok(Self {
40            wallet_pda: *wallet_pda,
41            new_root: *new_root,
42            payload,
43            account_addresses,
44        })
45    }
46
47    /// Wallet PDA this plan targets.
48    pub fn wallet_pda(&self) -> &Address {
49        &self.wallet_pda
50    }
51
52    /// Build a plan for the built-in lamport withdraw CPI.
53    pub fn withdraw(
54        wallet_pda: &Address,
55        receiver: &Address,
56        lamports: u64,
57        new_root: &[u8; 32],
58    ) -> Result<Self, Error> {
59        Self::new(
60            wallet_pda,
61            new_root,
62            &[withdraw(wallet_pda, receiver, lamports)],
63        )
64    }
65
66    /// Build a plan for the built-in close CPI, sweeping all lamports from the
67    /// wallet PDA to `receiver` and tearing the account down.
68    pub fn close(
69        wallet_pda: &Address,
70        receiver: &Address,
71        new_root: &[u8; 32],
72    ) -> Result<Self, Error> {
73        Self::new(wallet_pda, new_root, &[close(wallet_pda, receiver)])
74    }
75
76    /// The raw Advance payload committed by the signature.
77    pub fn payload(&self) -> &[u8] {
78        &self.payload.data
79    }
80
81    /// Passthrough accounts in the exact order expected by the on-chain handler.
82    pub fn passthrough_accounts(&self) -> &[AccountMeta] {
83        &self.payload.accounts
84    }
85
86    /// Account addresses included in the Advance preimage.
87    pub fn account_addresses(&self) -> &[[u8; 32]] {
88        &self.account_addresses
89    }
90
91    /// New root stored by the Advance instruction.
92    pub fn new_root(&self) -> &[u8; 32] {
93        &self.new_root
94    }
95
96    /// Build the preimage parts to sign for this plan.
97    pub fn preimage<'a>(
98        &'a self,
99        wallet_id: &'a [u8; 32],
100        current_root: &'a [u8; 32],
101    ) -> Vec<&'a [u8]> {
102        advance_preimage(
103            wallet_id,
104            current_root,
105            &self.new_root,
106            &self.account_addresses,
107            &self.payload.data,
108        )
109    }
110
111    /// Convert the plan into a signed Advance instruction.
112    pub fn instruction(&self, signature_bytes: &[u8; SIGNATURE_LEN]) -> Instruction {
113        advance(
114            &self.wallet_pda,
115            &self.payload.accounts,
116            signature_bytes,
117            &self.new_root,
118            &self.payload.data,
119        )
120    }
121
122    /// Estimate the legacy transaction size with default Advance compute budget.
123    pub fn estimate_transaction_size(
124        &self,
125        payer: &Address,
126        signature_bytes: &[u8; SIGNATURE_LEN],
127    ) -> Result<usize, Error> {
128        let ix = self.instruction(signature_bytes);
129        let ixs = with_compute_budget(&[ix], DEFAULT_ADVANCE_COMPUTE_UNIT_LIMIT, 0);
130        estimate_legacy_transaction_size(payer, &ixs)
131    }
132
133    /// Validate that the planned Advance fits in a legacy transaction.
134    pub fn validate_transaction_size(
135        &self,
136        payer: &Address,
137        signature_bytes: &[u8; SIGNATURE_LEN],
138    ) -> Result<usize, Error> {
139        let ix = self.instruction(signature_bytes);
140        let ixs = with_compute_budget(&[ix], DEFAULT_ADVANCE_COMPUTE_UNIT_LIMIT, 0);
141        validate_legacy_transaction_size(payer, &ixs)
142    }
143}