use solana_compute_budget_interface::ComputeBudgetInstruction;
use solana_message::{Hash, Instruction, Message, VersionedMessage, v0};
use solana_packet::PACKET_DATA_SIZE;
use solana_pubkey::Pubkey;
use solana_signer::{Signer, SignerError, signers::Signers};
use solana_system_interface::instruction as system_instruction;
use solana_transaction::sanitized::MAX_TX_ACCOUNT_LOCKS as SOLANA_MAX_TX_ACCOUNT_LOCKS;
use solana_transaction::versioned::VersionedTransaction;
use thiserror::Error;
pub const DEFAULT_DEVELOPER_TIP_LAMPORTS: u64 = 5_000;
pub const DEFAULT_DEVELOPER_TIP_RECIPIENT: Pubkey =
Pubkey::from_str_const("G3WHMVjx7Cb3MFhBAHe52zw8yhbHodWnas5gYLceaqze");
pub const MAX_TRANSACTION_WIRE_BYTES: usize = PACKET_DATA_SIZE;
pub const MAX_TRANSACTION_ACCOUNT_LOCKS: usize = SOLANA_MAX_TX_ACCOUNT_LOCKS;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TxMessageVersion {
Legacy,
#[default]
V0,
}
#[derive(Debug, Error)]
pub enum BuilderError {
#[error("failed to sign transaction: {source}")]
SignTransaction {
source: SignerError,
},
}
#[derive(Debug, Clone)]
pub struct UnsignedTx {
message: VersionedMessage,
}
impl UnsignedTx {
#[must_use]
pub const fn message(&self) -> &VersionedMessage {
&self.message
}
pub fn sign<T>(self, signers: &T) -> Result<VersionedTransaction, BuilderError>
where
T: Signers + ?Sized,
{
VersionedTransaction::try_new(self.message, signers)
.map_err(|source| BuilderError::SignTransaction { source })
}
}
#[derive(Debug, Clone)]
pub struct TxBuilder {
payer: Pubkey,
instructions: Vec<Instruction>,
compute_unit_limit: Option<u32>,
priority_fee_micro_lamports: Option<u64>,
developer_tip_lamports: Option<u64>,
developer_tip_recipient: Pubkey,
message_version: TxMessageVersion,
}
impl TxBuilder {
#[must_use]
pub const fn new(payer: Pubkey) -> Self {
Self {
payer,
instructions: Vec::new(),
compute_unit_limit: None,
priority_fee_micro_lamports: None,
developer_tip_lamports: None,
developer_tip_recipient: DEFAULT_DEVELOPER_TIP_RECIPIENT,
message_version: TxMessageVersion::V0,
}
}
#[must_use]
pub fn add_instruction(mut self, instruction: Instruction) -> Self {
self.instructions.push(instruction);
self
}
#[must_use]
pub fn add_instructions<I>(mut self, instructions: I) -> Self
where
I: IntoIterator<Item = Instruction>,
{
self.instructions.extend(instructions);
self
}
#[must_use]
pub const fn with_compute_unit_limit(mut self, units: u32) -> Self {
self.compute_unit_limit = Some(units);
self
}
#[must_use]
pub const fn without_compute_unit_limit(mut self) -> Self {
self.compute_unit_limit = None;
self
}
#[must_use]
pub const fn with_priority_fee_micro_lamports(mut self, micro_lamports: u64) -> Self {
self.priority_fee_micro_lamports = Some(micro_lamports);
self
}
#[must_use]
pub const fn without_priority_fee_micro_lamports(mut self) -> Self {
self.priority_fee_micro_lamports = None;
self
}
#[must_use]
pub const fn tip_developer(mut self) -> Self {
self.developer_tip_lamports = Some(DEFAULT_DEVELOPER_TIP_LAMPORTS);
self
}
#[must_use]
pub const fn tip_developer_lamports(mut self, lamports: u64) -> Self {
self.developer_tip_lamports = Some(lamports);
self
}
#[must_use]
pub const fn tip_to(mut self, recipient: Pubkey, lamports: u64) -> Self {
self.developer_tip_recipient = recipient;
self.developer_tip_lamports = Some(lamports);
self
}
#[must_use]
pub const fn with_message_version(mut self, version: TxMessageVersion) -> Self {
self.message_version = version;
self
}
#[must_use]
pub const fn with_legacy_message(self) -> Self {
self.with_message_version(TxMessageVersion::Legacy)
}
#[must_use]
pub const fn with_v0_message(self) -> Self {
self.with_message_version(TxMessageVersion::V0)
}
#[must_use]
pub fn build_unsigned(self, recent_blockhash: [u8; 32]) -> UnsignedTx {
UnsignedTx {
message: self.build_message(recent_blockhash),
}
}
pub fn build_and_sign<T>(
self,
recent_blockhash: [u8; 32],
signers: &T,
) -> Result<VersionedTransaction, BuilderError>
where
T: Signers + ?Sized,
{
self.build_unsigned(recent_blockhash).sign(signers)
}
#[must_use]
pub fn build_message(self, recent_blockhash: [u8; 32]) -> VersionedMessage {
let mut instructions = Vec::new();
if let Some(units) = self.compute_unit_limit {
instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(units));
}
if let Some(micro_lamports) = self.priority_fee_micro_lamports {
instructions.push(ComputeBudgetInstruction::set_compute_unit_price(
micro_lamports,
));
}
instructions.extend(self.instructions);
if let Some(lamports) = self.developer_tip_lamports {
instructions.push(system_instruction::transfer(
&self.payer,
&self.developer_tip_recipient,
lamports,
));
}
let blockhash = Hash::new_from_array(recent_blockhash);
let legacy_message =
Message::new_with_blockhash(&instructions, Some(&self.payer), &blockhash);
match self.message_version {
TxMessageVersion::Legacy => VersionedMessage::Legacy(legacy_message),
TxMessageVersion::V0 => VersionedMessage::V0(v0::Message {
header: legacy_message.header,
account_keys: legacy_message.account_keys,
recent_blockhash: legacy_message.recent_blockhash,
instructions: legacy_message.instructions,
address_table_lookups: Vec::new(),
}),
}
}
}
#[derive(Clone, Copy)]
pub struct SignerRef<'signer> {
signer: &'signer dyn Signer,
}
impl<'signer> SignerRef<'signer> {
#[must_use]
pub fn new(signer: &'signer dyn Signer) -> Self {
Self { signer }
}
#[must_use]
pub fn as_signer(self) -> &'signer dyn Signer {
self.signer
}
}