sof_solana_compat/
tx_builder.rs1use solana_compute_budget_interface::ComputeBudgetInstruction;
2use solana_message::{Hash, Instruction, Message, VersionedMessage, v0};
3use solana_packet::PACKET_DATA_SIZE;
4use solana_pubkey::Pubkey;
5use solana_signer::{Signer, SignerError, signers::Signers};
6use solana_system_interface::instruction as system_instruction;
7use solana_transaction::sanitized::MAX_TX_ACCOUNT_LOCKS as SOLANA_MAX_TX_ACCOUNT_LOCKS;
8use solana_transaction::versioned::VersionedTransaction;
9use thiserror::Error;
10
11pub const DEFAULT_DEVELOPER_TIP_LAMPORTS: u64 = 5_000;
13
14pub const DEFAULT_DEVELOPER_TIP_RECIPIENT: Pubkey =
16 Pubkey::from_str_const("G3WHMVjx7Cb3MFhBAHe52zw8yhbHodWnas5gYLceaqze");
17
18pub const MAX_TRANSACTION_WIRE_BYTES: usize = PACKET_DATA_SIZE;
20
21pub const MAX_TRANSACTION_ACCOUNT_LOCKS: usize = SOLANA_MAX_TX_ACCOUNT_LOCKS;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
26pub enum TxMessageVersion {
27 Legacy,
29 #[default]
31 V0,
32}
33
34#[derive(Debug, Error)]
36pub enum BuilderError {
37 #[error("failed to sign transaction: {source}")]
39 SignTransaction {
40 source: SignerError,
42 },
43}
44
45#[derive(Debug, Clone)]
47pub struct UnsignedTx {
48 message: VersionedMessage,
50}
51
52impl UnsignedTx {
53 #[must_use]
55 pub const fn message(&self) -> &VersionedMessage {
56 &self.message
57 }
58
59 pub fn sign<T>(self, signers: &T) -> Result<VersionedTransaction, BuilderError>
65 where
66 T: Signers + ?Sized,
67 {
68 VersionedTransaction::try_new(self.message, signers)
69 .map_err(|source| BuilderError::SignTransaction { source })
70 }
71}
72
73#[derive(Debug, Clone)]
75pub struct TxBuilder {
76 payer: Pubkey,
78 instructions: Vec<Instruction>,
80 compute_unit_limit: Option<u32>,
82 priority_fee_micro_lamports: Option<u64>,
84 developer_tip_lamports: Option<u64>,
86 developer_tip_recipient: Pubkey,
88 message_version: TxMessageVersion,
90}
91
92impl TxBuilder {
93 #[must_use]
95 pub const fn new(payer: Pubkey) -> Self {
96 Self {
97 payer,
98 instructions: Vec::new(),
99 compute_unit_limit: None,
100 priority_fee_micro_lamports: None,
101 developer_tip_lamports: None,
102 developer_tip_recipient: DEFAULT_DEVELOPER_TIP_RECIPIENT,
103 message_version: TxMessageVersion::V0,
104 }
105 }
106
107 #[must_use]
109 pub fn add_instruction(mut self, instruction: Instruction) -> Self {
110 self.instructions.push(instruction);
111 self
112 }
113
114 #[must_use]
116 pub fn add_instructions<I>(mut self, instructions: I) -> Self
117 where
118 I: IntoIterator<Item = Instruction>,
119 {
120 self.instructions.extend(instructions);
121 self
122 }
123
124 #[must_use]
126 pub const fn with_compute_unit_limit(mut self, units: u32) -> Self {
127 self.compute_unit_limit = Some(units);
128 self
129 }
130
131 #[must_use]
133 pub const fn without_compute_unit_limit(mut self) -> Self {
134 self.compute_unit_limit = None;
135 self
136 }
137
138 #[must_use]
140 pub const fn with_priority_fee_micro_lamports(mut self, micro_lamports: u64) -> Self {
141 self.priority_fee_micro_lamports = Some(micro_lamports);
142 self
143 }
144
145 #[must_use]
147 pub const fn without_priority_fee_micro_lamports(mut self) -> Self {
148 self.priority_fee_micro_lamports = None;
149 self
150 }
151
152 #[must_use]
154 pub const fn tip_developer(mut self) -> Self {
155 self.developer_tip_lamports = Some(DEFAULT_DEVELOPER_TIP_LAMPORTS);
156 self
157 }
158
159 #[must_use]
161 pub const fn tip_developer_lamports(mut self, lamports: u64) -> Self {
162 self.developer_tip_lamports = Some(lamports);
163 self
164 }
165
166 #[must_use]
168 pub const fn tip_to(mut self, recipient: Pubkey, lamports: u64) -> Self {
169 self.developer_tip_recipient = recipient;
170 self.developer_tip_lamports = Some(lamports);
171 self
172 }
173
174 #[must_use]
176 pub const fn with_message_version(mut self, version: TxMessageVersion) -> Self {
177 self.message_version = version;
178 self
179 }
180
181 #[must_use]
183 pub const fn with_legacy_message(self) -> Self {
184 self.with_message_version(TxMessageVersion::Legacy)
185 }
186
187 #[must_use]
189 pub const fn with_v0_message(self) -> Self {
190 self.with_message_version(TxMessageVersion::V0)
191 }
192
193 #[must_use]
195 pub fn build_unsigned(self, recent_blockhash: [u8; 32]) -> UnsignedTx {
196 UnsignedTx {
197 message: self.build_message(recent_blockhash),
198 }
199 }
200
201 pub fn build_and_sign<T>(
207 self,
208 recent_blockhash: [u8; 32],
209 signers: &T,
210 ) -> Result<VersionedTransaction, BuilderError>
211 where
212 T: Signers + ?Sized,
213 {
214 self.build_unsigned(recent_blockhash).sign(signers)
215 }
216
217 #[must_use]
219 pub fn build_message(self, recent_blockhash: [u8; 32]) -> VersionedMessage {
220 let mut instructions = Vec::new();
221 if let Some(units) = self.compute_unit_limit {
222 instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(units));
223 }
224 if let Some(micro_lamports) = self.priority_fee_micro_lamports {
225 instructions.push(ComputeBudgetInstruction::set_compute_unit_price(
226 micro_lamports,
227 ));
228 }
229 instructions.extend(self.instructions);
230 if let Some(lamports) = self.developer_tip_lamports {
231 instructions.push(system_instruction::transfer(
232 &self.payer,
233 &self.developer_tip_recipient,
234 lamports,
235 ));
236 }
237 let blockhash = Hash::new_from_array(recent_blockhash);
238 let legacy_message =
239 Message::new_with_blockhash(&instructions, Some(&self.payer), &blockhash);
240 match self.message_version {
241 TxMessageVersion::Legacy => VersionedMessage::Legacy(legacy_message),
242 TxMessageVersion::V0 => VersionedMessage::V0(v0::Message {
243 header: legacy_message.header,
244 account_keys: legacy_message.account_keys,
245 recent_blockhash: legacy_message.recent_blockhash,
246 instructions: legacy_message.instructions,
247 address_table_lookups: Vec::new(),
248 }),
249 }
250 }
251}
252
253#[derive(Clone, Copy)]
255pub struct SignerRef<'signer> {
256 signer: &'signer dyn Signer,
258}
259
260impl<'signer> SignerRef<'signer> {
261 #[must_use]
263 pub fn new(signer: &'signer dyn Signer) -> Self {
264 Self { signer }
265 }
266
267 #[must_use]
269 pub fn as_signer(self) -> &'signer dyn Signer {
270 self.signer
271 }
272}