1use std::{
2 collections::{HashMap, HashSet},
3 ops::Deref,
4};
5
6use solana_sdk::{
7 address_lookup_table::AddressLookupTableAccount,
8 commitment_config::CommitmentConfig,
9 hash::Hash,
10 instruction::{AccountMeta, Instruction},
11 message::{v0, VersionedMessage},
12 pubkey::Pubkey,
13 signer::Signer,
14 transaction::VersionedTransaction,
15};
16
17#[cfg(anchor)]
18use anchor_lang::prelude::*;
19
20#[cfg(client)]
21use solana_client::{nonblocking::rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig};
22
23#[cfg(client)]
24use solana_sdk::signature::Signature;
25
26use crate::{cluster::Cluster, compute_budget::ComputeBudget, signer::BoxClonableSigner};
27
28#[cfg(client)]
29use crate::{
30 bundle_builder::{BundleBuilder, BundleOptions, CreateBundleOptions},
31 client::SendAndConfirm,
32 utils::WithSlot,
33};
34
35#[derive(Clone)]
37pub struct Config<C> {
38 cluster: Cluster,
39 payer: C,
40 options: CommitmentConfig,
41}
42
43impl<C> Config<C> {
44 pub fn new(cluster: Cluster, payer: C, options: CommitmentConfig) -> Self {
46 Self {
47 cluster,
48 payer,
49 options,
50 }
51 }
52
53 pub fn cluster(&self) -> &Cluster {
55 &self.cluster
56 }
57
58 pub fn commitment(&self) -> &CommitmentConfig {
60 &self.options
61 }
62
63 #[cfg(client)]
65 pub fn rpc(&self) -> RpcClient {
66 self.cluster.rpc(self.options)
67 }
68
69 pub fn set_payer<C2>(self, payer: C2) -> Config<C2> {
71 Config {
72 cluster: self.cluster,
73 payer,
74 options: self.options,
75 }
76 }
77
78 pub fn set_cluster(mut self, url: impl AsRef<str>) -> crate::Result<Self> {
80 self.cluster = url.as_ref().parse()?;
81 Ok(self)
82 }
83
84 pub fn set_options(mut self, options: CommitmentConfig) -> Self {
86 self.options = options;
87 self
88 }
89}
90
91impl<C: Deref<Target = impl Signer>> Config<C> {
92 pub fn payer(&self) -> Pubkey {
94 self.payer.pubkey()
95 }
96}
97
98#[must_use = "transaction builder do nothing if not built"]
100#[derive(Clone)]
101pub struct TransactionBuilder<'a, C, T = ()> {
102 output: T,
103 program_id: Pubkey,
104 cfg: Config<C>,
105 signers: Vec<&'a dyn Signer>,
106 owned_signers: Vec<BoxClonableSigner<'static>>,
107 pre_instructions: Vec<Instruction>,
108 accounts: Vec<AccountMeta>,
109 instruction_data: Option<Vec<u8>>,
110 compute_budget: ComputeBudget,
111 luts: HashMap<Pubkey, Vec<Pubkey>>,
112}
113
114impl<'a, C: Deref<Target = impl Signer> + Clone> TransactionBuilder<'a, C> {
115 pub fn new(program_id: Pubkey, cfg: &'a Config<C>) -> Self {
117 Self {
118 output: (),
119 program_id,
120 cfg: cfg.clone(),
121 signers: Default::default(),
122 owned_signers: Default::default(),
123 pre_instructions: Default::default(),
124 accounts: Default::default(),
125 instruction_data: None,
126 compute_budget: ComputeBudget::default(),
127 luts: Default::default(),
128 }
129 }
130
131 #[inline]
139 pub fn merge(mut self, mut other: Self) -> Self {
140 self.try_merge(&mut other)
141 .unwrap_or_else(|err| panic!("failed to merge: {err}"));
142 self
143 }
144
145 pub fn try_merge(&mut self, other: &mut Self) -> crate::Result<()> {
154 if self.cfg.payer() != other.cfg.payer() {
155 return Err(crate::Error::MergeTransaction("payer mismatched"));
156 }
157
158 if let Some(ix) = self.take_instruction() {
160 self.pre_instructions.push(ix);
161 }
162
163 self.pre_instructions.append(&mut other.pre_instructions);
165 if let Some(ix) = other.take_instruction() {
166 self.pre_instructions.push(ix);
167 }
168
169 self.signers.append(&mut other.signers);
171
172 self.owned_signers.append(&mut other.owned_signers);
174
175 self.compute_budget += std::mem::take(&mut other.compute_budget);
177
178 self.luts.extend(other.luts.drain());
180 Ok(())
181 }
182
183 #[cfg(client)]
185 pub fn into_bundle_with_options(
186 self,
187 options: BundleOptions,
188 ) -> crate::Result<BundleBuilder<'a, C>> {
189 let mut bundle = BundleBuilder::new_with_options(CreateBundleOptions {
190 cluster: self.cfg.cluster.clone(),
191 commitment: *self.cfg.commitment(),
192 options,
193 });
194 bundle.push(self)?;
195 Ok(bundle)
196 }
197
198 #[cfg(client)]
200 pub fn into_bundle(self) -> crate::Result<BundleBuilder<'a, C>> {
201 self.into_bundle_with_options(Default::default())
202 }
203}
204
205impl<'a, C: Deref<Target = impl Signer> + Clone, T> TransactionBuilder<'a, C, T> {
206 pub fn payer<C2>(self, payer: C2) -> TransactionBuilder<'a, C2, T> {
208 TransactionBuilder {
209 output: self.output,
210 program_id: self.program_id,
211 cfg: self.cfg.set_payer(payer),
212 signers: self.signers,
213 owned_signers: self.owned_signers,
214 pre_instructions: self.pre_instructions,
215 accounts: self.accounts,
216 instruction_data: self.instruction_data,
217 compute_budget: self.compute_budget,
218 luts: self.luts,
219 }
220 }
221
222 pub fn get_payer(&self) -> Pubkey {
224 self.cfg.payer()
225 }
226
227 pub fn cluster(mut self, url: impl AsRef<str>) -> crate::Result<Self> {
229 self.cfg = self.cfg.set_cluster(url)?;
230 Ok(self)
231 }
232
233 pub fn options(mut self, options: CommitmentConfig) -> Self {
235 self.cfg = self.cfg.set_options(options);
236 self
237 }
238
239 pub fn signer(mut self, signer: &'a dyn Signer) -> Self {
241 self.signers.push(signer);
242 self
243 }
244
245 pub fn owned_signer(mut self, signer: impl Signer + Clone + 'static) -> Self {
247 self.owned_signers.push(BoxClonableSigner::new(signer));
248 self
249 }
250
251 pub fn program(mut self, program_id: Pubkey) -> Self {
253 self.program_id = program_id;
254 self
255 }
256
257 pub fn accounts(mut self, mut accounts: Vec<AccountMeta>) -> Self {
259 self.accounts.append(&mut accounts);
260 self
261 }
262
263 #[cfg(feature = "anchor")]
265 pub fn anchor_accounts(self, accounts: impl ToAccountMetas) -> Self {
266 self.accounts(accounts.to_account_metas(None))
267 }
268
269 pub fn args(mut self, args: Vec<u8>) -> Self {
271 self.instruction_data = Some(args);
272 self
273 }
274
275 #[cfg(feature = "anchor")]
277 pub fn anchor_args(self, args: impl anchor_lang::InstructionData) -> Self {
278 self.args(args.data())
279 }
280
281 pub fn compute_budget(mut self, budget: ComputeBudget) -> Self {
283 self.compute_budget = budget;
284 self
285 }
286
287 pub fn compute_budget_mut(&mut self) -> &mut ComputeBudget {
289 &mut self.compute_budget
290 }
291
292 fn get_compute_budget_instructions(
293 &self,
294 compute_unit_price_micro_lamports: Option<u64>,
295 ) -> Vec<Instruction> {
296 self.compute_budget
297 .compute_budget_instructions(compute_unit_price_micro_lamports)
298 }
299
300 pub fn take_instruction(&mut self) -> Option<Instruction> {
302 let ix_data = self.instruction_data.take()?;
303 Some(Instruction {
304 program_id: self.program_id,
305 data: ix_data,
306 accounts: std::mem::take(&mut self.accounts),
307 })
308 }
309
310 fn get_instruction(&self) -> Option<Instruction> {
312 let ix_data = self.instruction_data.as_ref()?;
313 Some(Instruction {
314 program_id: self.program_id,
315 data: ix_data.clone(),
316 accounts: self.accounts.clone(),
317 })
318 }
319
320 pub fn instructions(&self) -> Vec<Instruction> {
322 self.instructions_with_options(false, None)
323 }
324
325 pub fn instructions_with_options(
327 &self,
328 without_compute_budget: bool,
329 compute_unit_price_micro_lamports: Option<u64>,
330 ) -> Vec<Instruction> {
331 let mut instructions = if without_compute_budget {
332 Vec::default()
333 } else {
334 self.get_compute_budget_instructions(compute_unit_price_micro_lamports)
335 };
336 instructions.append(&mut self.pre_instructions.clone());
337 if let Some(ix) = self.get_instruction() {
338 instructions.push(ix);
339 }
340 instructions
341 }
342
343 pub fn get_output(&self) -> &T {
345 &self.output
346 }
347
348 pub fn swap_output<U>(self, output: U) -> (TransactionBuilder<'a, C, U>, T) {
350 let Self {
351 cfg,
352 signers,
353 owned_signers,
354 output: previous,
355 program_id,
356 pre_instructions,
357 accounts,
358 instruction_data,
359 compute_budget,
360 luts,
361 } = self;
362
363 (
364 TransactionBuilder {
365 cfg,
366 signers,
367 owned_signers,
368 output,
369 program_id,
370 pre_instructions,
371 accounts,
372 instruction_data,
373 compute_budget,
374 luts,
375 },
376 previous,
377 )
378 }
379
380 pub fn output<U>(self, output: U) -> TransactionBuilder<'a, C, U> {
382 self.swap_output(output).0
383 }
384
385 pub fn clear_output(self) -> TransactionBuilder<'a, C, ()> {
387 self.swap_output(()).0
388 }
389
390 pub fn pre_instruction(mut self, ix: Instruction) -> Self {
392 self.pre_instructions.push(ix);
393 self
394 }
395
396 pub fn pre_instructions(mut self, mut ixs: Vec<Instruction>) -> Self {
398 self.pre_instructions.append(&mut ixs);
399 self
400 }
401
402 pub fn lookup_table(mut self, account: AddressLookupTableAccount) -> Self {
404 self.luts.insert(account.key, account.addresses);
405 self
406 }
407
408 pub fn lookup_tables(
410 mut self,
411 tables: impl IntoIterator<Item = (Pubkey, Vec<Pubkey>)>,
412 ) -> Self {
413 self.luts.extend(tables);
414 self
415 }
416
417 fn v0_message_with_blockhash_and_options(
418 &self,
419 latest_hash: Hash,
420 without_compute_budget: bool,
421 compute_unit_price_micro_lamports: Option<u64>,
422 with_luts: bool,
423 ) -> crate::Result<v0::Message> {
424 let instructions = self
425 .instructions_with_options(without_compute_budget, compute_unit_price_micro_lamports);
426 let luts = if with_luts {
427 self.luts
428 .iter()
429 .map(|(key, addresses)| AddressLookupTableAccount {
430 key: *key,
431 addresses: addresses.clone(),
432 })
433 .collect::<Vec<_>>()
434 } else {
435 vec![]
436 };
437 let message =
438 v0::Message::try_compile(&self.cfg.payer(), &instructions, &luts, latest_hash)?;
439
440 Ok(message)
441 }
442
443 pub fn message_with_blockhash_and_options(
445 &self,
446 latest_hash: Hash,
447 without_compute_budget: bool,
448 compute_unit_price_micro_lamports: Option<u64>,
449 ) -> crate::Result<VersionedMessage> {
450 Ok(VersionedMessage::V0(
451 self.v0_message_with_blockhash_and_options(
452 latest_hash,
453 without_compute_budget,
454 compute_unit_price_micro_lamports,
455 true,
456 )?,
457 ))
458 }
459
460 #[cfg(client)]
462 pub async fn message_with_options(
463 &self,
464 without_compute_budget: bool,
465 compute_unit_price_micro_lamports: Option<u64>,
466 ) -> crate::Result<VersionedMessage> {
467 let client = self.cfg.rpc();
468 let latest_hash = client.get_latest_blockhash().await.map_err(Box::new)?;
469
470 self.message_with_blockhash_and_options(
471 latest_hash,
472 without_compute_budget,
473 compute_unit_price_micro_lamports,
474 )
475 }
476
477 pub fn signed_transaction_with_blockhash_and_options(
479 &self,
480 latest_hash: Hash,
481 without_compute_budget: bool,
482 compute_unit_price_micro_lamports: Option<u64>,
483 ) -> crate::Result<VersionedTransaction> {
484 let message = self.message_with_blockhash_and_options(
485 latest_hash,
486 without_compute_budget,
487 compute_unit_price_micro_lamports,
488 )?;
489
490 let mut signers = self.signers.clone();
491 signers.push(&*self.cfg.payer);
492 for signer in self.owned_signers.iter() {
493 signers.push(signer);
494 }
495
496 let tx = VersionedTransaction::try_new(message, &signers)?;
497
498 Ok(tx)
499 }
500
501 #[cfg(client)]
503 pub async fn signed_transaction_with_options(
504 &self,
505 without_compute_budget: bool,
506 compute_unit_price_micro_lamports: Option<u64>,
507 ) -> crate::Result<VersionedTransaction> {
508 let client = self.cfg.rpc();
509 let latest_hash = client.get_latest_blockhash().await.map_err(Box::new)?;
510
511 self.signed_transaction_with_blockhash_and_options(
512 latest_hash,
513 without_compute_budget,
514 compute_unit_price_micro_lamports,
515 )
516 }
517
518 #[cfg(client)]
520 pub async fn send_with_options(
521 &self,
522 without_compute_budget: bool,
523 compute_unit_price_micro_lamports: Option<u64>,
524 mut config: RpcSendTransactionConfig,
525 ) -> crate::Result<WithSlot<Signature>> {
526 let client = self.cfg.rpc();
527 let latest_hash = client.get_latest_blockhash().await.map_err(Box::new)?;
528
529 let tx = self.signed_transaction_with_blockhash_and_options(
530 latest_hash,
531 without_compute_budget,
532 compute_unit_price_micro_lamports,
533 )?;
534
535 config.preflight_commitment = config
536 .preflight_commitment
537 .or(Some(client.commitment().commitment));
538
539 let signature = client
540 .send_and_confirm_transaction_with_config(&tx, config)
541 .await
542 .map_err(Box::new)?;
543
544 Ok(signature)
545 }
546
547 #[cfg(client)]
549 pub async fn send_without_preflight(self) -> crate::Result<Signature> {
550 Ok(self
551 .send_with_options(
552 false,
553 None,
554 RpcSendTransactionConfig {
555 skip_preflight: true,
556 ..Default::default()
557 },
558 )
559 .await?
560 .into_value())
561 }
562
563 #[cfg(client)]
565 pub async fn send(self) -> crate::Result<Signature> {
566 Ok(self
567 .send_with_options(false, None, Default::default())
568 .await?
569 .into_value())
570 }
571
572 pub fn get_complete_lookup_table(&self) -> HashSet<Pubkey> {
574 self.luts
575 .values()
576 .flatten()
577 .copied()
578 .collect::<HashSet<_>>()
579 }
580
581 pub fn get_luts(&self) -> &HashMap<Pubkey, Vec<Pubkey>> {
583 &self.luts
584 }
585
586 pub fn transaction_size(&self, is_versioned_transaction: bool) -> usize {
590 let lookup_table = self.get_complete_lookup_table();
591 crate::utils::transaction_size(
592 &self.instructions(),
593 is_versioned_transaction,
594 Some(&lookup_table),
595 self.get_luts().len(),
596 )
597 }
598
599 #[cfg(client)]
601 pub async fn estimate_execution_fee(
602 &self,
603 _client: &RpcClient,
604 compute_unit_price_micro_lamports: Option<u64>,
605 ) -> crate::Result<u64> {
606 let ixs = self.instructions_with_options(true, None);
607
608 let num_signers = ixs
609 .iter()
610 .flat_map(|ix| ix.accounts.iter())
611 .filter(|meta| meta.is_signer)
612 .map(|meta| &meta.pubkey)
613 .collect::<HashSet<_>>()
614 .len() as u64;
615 let fee = num_signers * 5_000 + self.compute_budget.fee(compute_unit_price_micro_lamports);
616 Ok(fee)
617 }
618}