winterwallet_client/
transaction.rs1use solana_address::Address;
2use solana_instruction::Instruction;
3
4use crate::Error;
5
6pub const LEGACY_TRANSACTION_SIZE_LIMIT: usize = 1232;
8
9pub const DEFAULT_ADVANCE_COMPUTE_UNIT_LIMIT: u32 = 800_000;
12
13pub fn set_compute_unit_limit(units: u32) -> Instruction {
15 let mut data = Vec::with_capacity(5);
16 data.push(0x02);
17 data.extend_from_slice(&units.to_le_bytes());
18 Instruction {
19 program_id: compute_budget_program_id(),
20 accounts: vec![],
21 data,
22 }
23}
24
25pub fn set_compute_unit_price(micro_lamports: u64) -> Instruction {
27 let mut data = Vec::with_capacity(9);
28 data.push(0x03);
29 data.extend_from_slice(µ_lamports.to_le_bytes());
30 Instruction {
31 program_id: compute_budget_program_id(),
32 accounts: vec![],
33 data,
34 }
35}
36
37pub fn with_compute_budget(
39 instructions: &[Instruction],
40 unit_limit: u32,
41 unit_price_micro_lamports: u64,
42) -> Vec<Instruction> {
43 let mut out = Vec::with_capacity(instructions.len() + 2);
44 out.push(set_compute_unit_limit(unit_limit));
45 out.push(set_compute_unit_price(unit_price_micro_lamports));
46 out.extend_from_slice(instructions);
47 out
48}
49
50pub fn estimate_legacy_transaction_size(
55 payer: &Address,
56 instructions: &[Instruction],
57) -> Result<usize, Error> {
58 let accounts = collect_accounts(payer, instructions)?;
59 let required_signatures = accounts.iter().filter(|a| a.is_signer).count();
60 let message_size = legacy_message_size(&accounts, instructions)?;
61 Ok(compact_u16_len(required_signatures) + required_signatures * 64 + message_size)
62}
63
64pub fn validate_legacy_transaction_size(
66 payer: &Address,
67 instructions: &[Instruction],
68) -> Result<usize, Error> {
69 let estimated = estimate_legacy_transaction_size(payer, instructions)?;
70 if estimated > LEGACY_TRANSACTION_SIZE_LIMIT {
71 return Err(Error::TransactionTooLarge {
72 estimated,
73 limit: LEGACY_TRANSACTION_SIZE_LIMIT,
74 });
75 }
76 Ok(estimated)
77}
78
79pub fn validate_payer_only_signers(
81 payer: &Address,
82 instructions: &[Instruction],
83) -> Result<(), Error> {
84 for ix in instructions {
85 for meta in &ix.accounts {
86 if meta.is_signer && meta.pubkey != *payer {
87 return Err(Error::UnsupportedTransaction(
88 "transaction requires a non-payer signature",
89 ));
90 }
91 }
92 }
93 Ok(())
94}
95
96fn compute_budget_program_id() -> Address {
97 solana_address::address!("ComputeBudget111111111111111111111111111111")
98}
99
100#[derive(Clone)]
101pub struct AccountEntry {
102 pub pubkey: Address,
103 pub is_signer: bool,
104 pub is_writable: bool,
105}
106
107fn collect_accounts(
108 payer: &Address,
109 instructions: &[Instruction],
110) -> Result<Vec<AccountEntry>, Error> {
111 let mut accounts = Vec::new();
112 upsert(&mut accounts, payer, true, true);
113
114 for ix in instructions {
115 upsert(&mut accounts, &ix.program_id, false, false);
116 for meta in &ix.accounts {
117 upsert(
118 &mut accounts,
119 &meta.pubkey,
120 meta.is_signer,
121 meta.is_writable,
122 );
123 }
124 }
125
126 accounts.sort_by_key(|entry| match (entry.is_signer, entry.is_writable) {
127 (true, true) => 0,
128 (true, false) => 1,
129 (false, true) => 2,
130 (false, false) => 3,
131 });
132
133 if accounts.len() > u8::MAX as usize + 1 {
134 return Err(Error::UnsupportedTransaction(
135 "legacy message account indexes exceed u8 range",
136 ));
137 }
138
139 Ok(accounts)
140}
141
142fn legacy_message_size(
143 accounts: &[AccountEntry],
144 instructions: &[Instruction],
145) -> Result<usize, Error> {
146 let mut size = 3; size += compact_u16_len(accounts.len());
148 size += accounts.len() * 32;
149 size += 32; size += compact_u16_len(instructions.len());
151
152 for ix in instructions {
153 size += 1; size += compact_u16_len(ix.accounts.len());
155 size += ix.accounts.len(); size += compact_u16_len(ix.data.len());
157 size += ix.data.len();
158
159 if accounts.iter().all(|a| a.pubkey != ix.program_id) {
160 return Err(Error::UnsupportedTransaction(
161 "instruction program id missing from account list",
162 ));
163 }
164 for meta in &ix.accounts {
165 if accounts.iter().all(|a| a.pubkey != meta.pubkey) {
166 return Err(Error::UnsupportedTransaction(
167 "instruction account missing from account list",
168 ));
169 }
170 }
171 }
172
173 Ok(size)
174}
175
176pub fn upsert(
177 accounts: &mut Vec<AccountEntry>,
178 pubkey: &Address,
179 is_signer: bool,
180 is_writable: bool,
181) {
182 if let Some(existing) = accounts.iter_mut().find(|a| a.pubkey == *pubkey) {
183 existing.is_signer |= is_signer;
184 existing.is_writable |= is_writable;
185 } else {
186 accounts.push(AccountEntry {
187 pubkey: *pubkey,
188 is_signer,
189 is_writable,
190 });
191 }
192}
193
194fn compact_u16_len(value: usize) -> usize {
195 let mut value = value;
196 let mut len = 1;
197 while value >= 0x80 {
198 value >>= 7;
199 len += 1;
200 }
201 len
202}