solders_transaction/
lib.rs

1#![allow(deprecated)]
2use derive_more::{From, Into};
3use pyo3::{prelude::*, types::PyBytes};
4use serde::{Deserialize, Serialize};
5use solana_sdk::{
6    pubkey::Pubkey as PubkeyOriginal,
7    sanitize::Sanitize,
8    signature::Signature as SignatureOriginal,
9    transaction::{
10        get_nonce_pubkey_from_instruction, uses_durable_nonce, Legacy as LegacyOriginal,
11        Transaction as TransactionOriginal, TransactionVersion as TransactionVersionOriginal,
12        VersionedTransaction as VersionedTransactionOriginal,
13    },
14};
15use solders_macros::{common_methods, richcmp_eq_only, EnumIntoPy};
16use solders_pubkey::{convert_optional_pubkey, Pubkey};
17use solders_traits::handle_py_err;
18use solders_traits_core::{
19    impl_display, py_from_bytes_general_via_bincode, pybytes_general_via_bincode,
20    CommonMethodsCore, RichcmpEqualityOnly,
21};
22
23use solders_hash::Hash as SolderHash;
24use solders_instruction::{convert_instructions, CompiledInstruction, Instruction};
25use solders_keypair::signer::{Signer, SignerVec};
26use solders_message::{Message, VersionedMessage};
27use solders_signature::{originals_into_solders, solders_into_originals, Signature};
28
29/// An atomic transaction
30///
31/// The ``__init__`` method signs a versioned message to
32/// create a signed transaction.
33///
34/// Args:
35///     message (Message | MessageV0): The message to sign.
36///     keypairs (Sequence[Keypair | Presigner]): The keypairs that are to sign the transaction.
37#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize, From, Into)]
38#[pyclass(module = "solders.transaction", subclass)]
39pub struct VersionedTransaction(pub VersionedTransactionOriginal);
40
41impl From<Transaction> for VersionedTransaction {
42    fn from(t: Transaction) -> Self {
43        VersionedTransactionOriginal::from(TransactionOriginal::from(t)).into()
44    }
45}
46
47impl RichcmpEqualityOnly for VersionedTransaction {}
48pybytes_general_via_bincode!(VersionedTransaction);
49py_from_bytes_general_via_bincode!(VersionedTransaction);
50impl_display!(VersionedTransaction);
51solders_traits_core::common_methods_default!(VersionedTransaction);
52
53#[richcmp_eq_only]
54#[common_methods]
55#[pymethods]
56impl VersionedTransaction {
57    #[new]
58    pub fn new(message: VersionedMessage, keypairs: Vec<Signer>) -> PyResult<Self> {
59        handle_py_err(VersionedTransactionOriginal::try_new(
60            message.into(),
61            &SignerVec(keypairs),
62        ))
63    }
64
65    /// Message | MessageV0: The transaction message.
66    #[getter]
67    pub fn message(&self) -> VersionedMessage {
68        self.0.message.clone().into()
69    }
70
71    /// List[Signature]: The transaction signatures.
72    #[getter]
73    pub fn signatures(&self) -> Vec<Signature> {
74        originals_into_solders(self.0.signatures.clone())
75    }
76
77    #[setter]
78    fn set_signatures(&mut self, signatures: Vec<Signature>) {
79        self.0.signatures = solders_into_originals(signatures);
80    }
81
82    /// Create a fully-signed transaction from a message and its signatures.
83    ///
84    /// Args:
85    ///     message (Message | MessageV0): The transaction message.
86    ///     signatures (Sequence[Signature]): The message's signatures.
87    ///
88    /// Returns:
89    ///     Transaction: The signed transaction.
90    ///
91    /// Example:
92    ///
93    ///     >>> from solders.pubkey import Pubkey
94    ///     >>> from solders.instruction import Instruction
95    ///     >>> from solders.message import MessageV0
96    ///     >>> from solders.hash import Hash
97    ///     >>> from solders.keypair import Keypair
98    ///     >>> from solders.transaction import VersionedTransaction
99    ///     >>> payer = Keypair()
100    ///     >>> program_id = Pubkey.default()
101    ///     >>> instructions = [Instruction(program_id, bytes([]), [])]
102    ///     >>> recent_blockhash = Hash.new_unique()
103    ///     >>> message = MessageV0.try_compile(payer.pubkey(), instructions, [], recent_blockhash)
104    ///     >>> tx = VersionedTransaction(message, [payer])
105    ///     >>> assert VersionedTransaction.populate(message, tx.signatures) == tx
106    ///
107    #[staticmethod]
108    pub fn populate(message: VersionedMessage, signatures: Vec<Signature>) -> Self {
109        VersionedTransactionOriginal {
110            signatures: signatures.into_iter().map(|s| s.into()).collect(),
111            message: message.into(),
112        }
113        .into()
114    }
115
116    /// Sanity checks the Transaction properties.
117    pub fn sanitize(&self) -> PyResult<()> {
118        handle_py_err(self.0.sanitize())
119    }
120
121    /// Returns the version of the transaction.
122    ///
123    /// Returns:
124    ///     Legacy | int: Transaction version.
125    pub fn version(&self) -> TransactionVersion {
126        self.0.version().into()
127    }
128
129    /// Returns a legacy transaction if the transaction message is legacy.
130    ///
131    /// Returns:
132    ///     Optional[Transaction]: The legacy transaction.
133    pub fn into_legacy_transaction(&self) -> Option<Transaction> {
134        self.0.clone().into_legacy_transaction().map(|t| t.into())
135    }
136
137    /// Verify the transaction and hash its message
138    pub fn verify_and_hash_message(&self) -> PyResult<SolderHash> {
139        handle_py_err(self.0.verify_and_hash_message())
140    }
141
142    /// Verify the transaction and return a list of verification results
143    pub fn verify_with_results(&self) -> Vec<bool> {
144        self.0.verify_with_results()
145    }
146
147    #[staticmethod]
148    #[pyo3(name = "default")]
149    /// Return a new default transaction.
150    ///
151    /// Returns:
152    ///     VersionedTransaction: The default transaction.
153    pub fn new_default() -> Self {
154        Self::default()
155    }
156
157    /// Convert a legacy transaction to a VersionedTransaction.
158    ///
159    /// Returns:
160    ///     VersionedTransaction: The versioned tx.
161    #[staticmethod]
162    pub fn from_legacy(tx: Transaction) -> Self {
163        Self::from(tx)
164    }
165
166    /// Returns true if transaction begins with a valid advance nonce instruction.
167    ///
168    /// Returns:
169    ///     bool
170    pub fn uses_durable_nonce(&self) -> bool {
171        self.0.uses_durable_nonce()
172    }
173}
174
175#[pyclass(module = "solders.transaction", subclass)]
176#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize, From, Into)]
177/// An atomically-commited sequence of instructions.
178///
179/// While :class:`~solders.instruction.Instruction`\s are the basic unit of computation in Solana,
180/// they are submitted by clients in :class:`~solders.transaction.Transaction`\s containing one or
181/// more instructions, and signed by one or more signers.
182///
183///
184/// See the `Rust module documentation <https://docs.rs/solana-sdk/latest/solana_sdk/transaction/index.html>`_ for more details about transactions.
185///
186/// Some constructors accept an optional ``payer``, the account responsible for
187/// paying the cost of executing a transaction. In most cases, callers should
188/// specify the payer explicitly in these constructors. In some cases though,
189/// the caller is not *required* to specify the payer, but is still allowed to:
190/// in the :class:`~solders.message.Message` object, the first account is always the fee-payer, so
191/// if the caller has knowledge that the first account of the constructed
192/// transaction's ``Message`` is both a signer and the expected fee-payer, then
193/// redundantly specifying the fee-payer is not strictly required.
194///
195/// The main ``Transaction()`` constructor creates a fully-signed transaction from a ``Message``.
196///
197/// Args:
198///     from_keypairs (Sequence[Keypair | Presigner]): The keypairs that are to sign the transaction.
199///     message (Message): The message to sign.
200///     recent_blockhash (Hash): The id of a recent ledger entry.
201///
202/// Example:
203///     >>> from solders.message import Message
204///     >>> from solders.keypair import Keypair
205///     >>> from solders.instruction import Instruction
206///     >>> from solders.hash import Hash
207///     >>> from solders.transaction import Transaction
208///     >>> from solders.pubkey import Pubkey
209///     >>> program_id = Pubkey.default()
210///     >>> arbitrary_instruction_data = bytes([1])
211///     >>> accounts = []
212///     >>> instruction = Instruction(program_id, arbitrary_instruction_data, accounts)
213///     >>> payer = Keypair()
214///     >>> message = Message([instruction], payer.pubkey())
215///     >>> blockhash = Hash.default()  # replace with a real blockhash
216///     >>> tx = Transaction([payer], message, blockhash)
217///
218pub struct Transaction(pub TransactionOriginal);
219
220#[richcmp_eq_only]
221#[common_methods]
222#[pymethods]
223impl Transaction {
224    #[new]
225    pub fn new(
226        from_keypairs: Vec<Signer>,
227        message: &Message,
228        recent_blockhash: SolderHash,
229    ) -> Self {
230        TransactionOriginal::new(
231            &SignerVec(from_keypairs),
232            message.into(),
233            recent_blockhash.into(),
234        )
235        .into()
236    }
237
238    #[getter]
239    /// list[Signature]: A set of signatures of a serialized :class:`~solders.message.Message`,
240    /// signed by the first keys of the message's :attr:`~solders.message.Message.account_keys`,
241    /// where the number of signatures is equal to ``num_required_signatures`` of the `Message`'s
242    /// :class:`~solders.message.MessageHeader`.
243    pub fn signatures(&self) -> Vec<Signature> {
244        originals_into_solders(self.0.signatures.clone())
245    }
246
247    #[setter]
248    fn set_signatures(&mut self, signatures: Vec<Signature>) {
249        self.0.signatures = solders_into_originals(signatures);
250    }
251
252    #[getter]
253    /// Message: The message to sign.
254    pub fn message(&self) -> Message {
255        self.0.message.clone().into()
256    }
257
258    #[staticmethod]
259    /// Create an unsigned transaction from a :class:`~solders.message.Message`.
260    ///
261    /// Args:
262    ///     message (Message): The transaction's message.
263    ///
264    /// Returns:
265    ///     Transaction: The unsigned transaction.
266    ///
267    /// Example:
268    ///     >>> from typing import List
269    ///     >>> from solders.message import Message
270    ///     >>> from solders.keypair import Keypair
271    ///     >>> from solders.pubkey import Pubkey
272    ///     >>> from solders.instruction import Instruction, AccountMeta
273    ///     >>> from solders.hash import Hash
274    ///     >>> from solders.transaction import Transaction
275    ///     >>> program_id = Pubkey.default()
276    ///     >>> blockhash = Hash.default()  # replace with a real blockhash
277    ///     >>> arbitrary_instruction_data = bytes([1])
278    ///     >>> accounts: List[AccountMeta] = []
279    ///     >>> instruction = Instruction(program_id, arbitrary_instruction_data, accounts)
280    ///     >>> payer = Keypair()
281    ///     >>> message = Message.new_with_blockhash([instruction], payer.pubkey(), blockhash)
282    ///     >>> tx = Transaction.new_unsigned(message)
283    ///     >>> tx.sign([payer], tx.message.recent_blockhash)
284    ///
285    pub fn new_unsigned(message: Message) -> Self {
286        TransactionOriginal::new_unsigned(message.into()).into()
287    }
288
289    #[staticmethod]
290    /// Create an unsigned transaction from a list of :class:`~solders.instruction.Instruction`\s.
291    ///
292    /// Args:
293    ///    instructions (Sequence[Instruction]): The instructions to include in the transaction message.
294    ///    payer (Optional[Pubkey], optional): The transaction fee payer. Defaults to None.
295    ///
296    /// Returns:
297    ///     Transaction: The unsigned transaction.
298    ///
299    /// Example:
300    ///     >>> from solders.keypair import Keypair
301    ///     >>> from solders.instruction import Instruction
302    ///     >>> from solders.transaction import Transaction
303    ///     >>> from solders.pubkey import Pubkey
304    ///     >>> program_id = Pubkey.default()
305    ///     >>> arbitrary_instruction_data = bytes([1])
306    ///     >>> accounts = []
307    ///     >>> instruction = Instruction(program_id, arbitrary_instruction_data, accounts)
308    ///     >>> payer = Keypair()
309    ///     >>> tx = Transaction.new_with_payer([instruction], payer.pubkey())
310    ///
311    pub fn new_with_payer(instructions: Vec<Instruction>, payer: Option<&Pubkey>) -> Self {
312        TransactionOriginal::new_with_payer(
313            &convert_instructions(instructions),
314            convert_optional_pubkey(payer),
315        )
316        .into()
317    }
318
319    #[staticmethod]
320    /// Create a fully-signed transaction from a list of :class:`~solders.instruction.Instruction`\s.
321    ///
322    /// Args:
323    ///    instructions (Sequence[Instruction]): The instructions to include in the transaction message.
324    ///    payer (Optional[Pubkey], optional): The transaction fee payer.
325    ///    signing_keypairs (Sequence[Keypair | Presigner]): The keypairs that will sign the transaction.
326    ///    recent_blockhash (Hash): The id of a recent ledger entry.
327    ///    
328    /// Returns:
329    ///     Transaction: The signed transaction.
330    ///
331    ///
332    /// Example:
333    ///     >>> from solders.keypair import Keypair
334    ///     >>> from solders.instruction import Instruction
335    ///     >>> from solders.transaction import Transaction
336    ///     >>> from solders.pubkey import Pubkey
337    ///     >>> program_id = Pubkey.default()
338    ///     >>> arbitrary_instruction_data = bytes([1])
339    ///     >>> accounts = []
340    ///     >>> instruction = Instruction(program_id, arbitrary_instruction_data, accounts)
341    ///     >>> payer = Keypair()
342    ///     >>> blockhash = Hash.default()  # replace with a real blockhash
343    ///     >>> tx = Transaction.new_signed_with_payer([instruction], payer.pubkey(), [payer], blockhash);
344    ///
345    #[pyo3(signature = (instructions, payer, signing_keypairs, recent_blockhash))]
346    pub fn new_signed_with_payer(
347        instructions: Vec<Instruction>,
348        payer: Option<Pubkey>,
349        signing_keypairs: Vec<Signer>,
350        recent_blockhash: SolderHash,
351    ) -> Self {
352        TransactionOriginal::new_signed_with_payer(
353            &convert_instructions(instructions),
354            convert_optional_pubkey(payer.as_ref()),
355            &SignerVec(signing_keypairs),
356            recent_blockhash.into(),
357        )
358        .into()
359    }
360
361    #[staticmethod]
362    /// Create a fully-signed transaction from pre-compiled instructions.
363    ///
364    /// Args:
365    ///     from_keypairs (Sequence[Keypair | Presigner]): The keys used to sign the transaction.
366    ///     keys (Sequence[Pubkey]): The keys for the transaction.  These are the program state
367    ///         instances or lamport recipient keys.
368    ///     recent_blockhash (Hash): The PoH hash.
369    ///     program_ids (Sequence[Pubkey]): The keys that identify programs used in the `instruction` vector.
370    ///     instructions (Sequence[Instruction]): Instructions that will be executed atomically.
371    ///
372    /// Returns:
373    ///     Transaction: The signed transaction.
374    ///
375    pub fn new_with_compiled_instructions(
376        from_keypairs: Vec<Signer>,
377        keys: Vec<Pubkey>,
378        recent_blockhash: SolderHash,
379        program_ids: Vec<Pubkey>,
380        instructions: Vec<CompiledInstruction>,
381    ) -> Self {
382        let converted_keys: Vec<PubkeyOriginal> =
383            keys.into_iter().map(PubkeyOriginal::from).collect();
384        let converted_program_ids: Vec<PubkeyOriginal> =
385            program_ids.into_iter().map(PubkeyOriginal::from).collect();
386        let converted_instructions = instructions
387            .into_iter()
388            .map(solana_sdk::instruction::CompiledInstruction::from)
389            .collect();
390        TransactionOriginal::new_with_compiled_instructions(
391            &SignerVec(from_keypairs),
392            &converted_keys,
393            recent_blockhash.into(),
394            converted_program_ids,
395            converted_instructions,
396        )
397        .into()
398    }
399
400    #[staticmethod]
401    /// Create a fully-signed transaction from a message and its signatures.
402    ///
403    /// Args:
404    ///     message (Message): The transaction message.
405    ///     signatures (Sequence[Signature]): The message's signatures.
406    ///
407    /// Returns:
408    ///     Message: The signed transaction.
409    ///
410    /// Example:
411    ///
412    ///     >>> from solders.keypair import Keypair
413    ///     >>> from solders.instruction import Instruction
414    ///     >>> from solders.transaction import Transaction
415    ///     >>> from solders.pubkey import Pubkey
416    ///     >>> program_id = Pubkey.default()
417    ///     >>> arbitrary_instruction_data = bytes([1])
418    ///     >>> accounts = []
419    ///     >>> instruction = Instruction(program_id, arbitrary_instruction_data, accounts)
420    ///     >>> payer = Keypair()
421    ///     >>> blockhash = Hash.default()  # replace with a real blockhash
422    ///     >>> tx = Transaction.new_signed_with_payer([instruction], payer.pubkey(), [payer], blockhash);
423    ///     >>> assert tx == Transaction.populate(tx.message, tx.signatures)
424    ///
425    pub fn populate(message: Message, signatures: Vec<Signature>) -> Self {
426        (TransactionOriginal {
427            message: message.into(),
428            signatures: signatures
429                .into_iter()
430                .map(SignatureOriginal::from)
431                .collect(),
432        })
433        .into()
434    }
435
436    /// Get the data for an instruction at the given index.
437    ///
438    /// Args:
439    ///     instruction_index (int): index into the ``instructions`` vector of the transaction's ``message``.
440    ///
441    /// Returns:
442    ///     bytes: The instruction data.
443    ///
444    pub fn data(&self, instruction_index: usize) -> &[u8] {
445        self.0.data(instruction_index)
446    }
447
448    /// Get the :class:`~solders.pubkey.Pubkey` of an account required by one of the instructions in
449    /// the transaction.
450    ///
451    /// Returns ``None`` if `instruction_index` is greater than or equal to the
452    /// number of instructions in the transaction; or if `accounts_index` is
453    /// greater than or equal to the number of accounts in the instruction.
454    ///
455    /// Args:
456    ///     instruction_index (int): index into the ``instructions`` vector of the transaction's ``message``.
457    ///     account_index (int): index into the ``acounts`` list of the message's ``compiled_instructions``.
458    ///
459    /// Returns:
460    ///     Optional[Pubkey]: The account key.
461    ///
462    pub fn key(&self, instruction_index: usize, accounts_index: usize) -> Option<Pubkey> {
463        self.0
464            .key(instruction_index, accounts_index)
465            .map(Pubkey::from)
466    }
467
468    /// Get the :class:`~solders.pubkey.Pubkey` of a signing account required by one of the
469    /// instructions in the transaction.
470    ///
471    /// The transaction does not need to be signed for this function to return a
472    /// signing account's pubkey.
473    ///
474    /// Returns ``None`` if the indexed account is not required to sign the
475    /// transaction. Returns ``None`` if the [`signatures`] field does not contain
476    /// enough elements to hold a signature for the indexed account (this should
477    /// only be possible if `Transaction` has been manually constructed).
478    ///
479    /// Returns `None` if `instruction_index` is greater than or equal to the
480    /// number of instructions in the transaction; or if `accounts_index` is
481    /// greater than or equal to the number of accounts in the instruction.
482    ///
483    /// Args:
484    ///     instruction_index (int): index into the ``instructions`` vector of the transaction's ``message``.
485    ///     account_index (int): index into the ``acounts`` list of the message's ``compiled_instructions``.
486    ///
487    /// Returns:
488    ///     Optional[Pubkey]: The account key.
489    ///
490    pub fn signer_key(&self, instruction_index: usize, accounts_index: usize) -> Option<Pubkey> {
491        self.0
492            .signer_key(instruction_index, accounts_index)
493            .map(Pubkey::from)
494    }
495
496    /// Return the serialized message data to sign.
497    ///
498    /// Returns:
499    ///     bytes: The serialized message data.
500    ///
501    pub fn message_data<'a>(&self, py: Python<'a>) -> &'a PyBytes {
502        PyBytes::new(py, &self.0.message_data())
503    }
504
505    /// Sign the transaction, returning any errors.
506    ///
507    /// This method fully signs a transaction with all required signers, which
508    /// must be present in the ``keypairs`` list. To sign with only some of the
509    /// required signers, use :meth:`Transaction.partial_sign`.
510    ///
511    /// If ``recent_blockhash`` is different than recorded in the transaction message's
512    /// ``recent_blockhash``] field, then the message's ``recent_blockhash`` will be updated
513    /// to the provided ``recent_blockhash``, and any prior signatures will be cleared.
514    ///
515    ///
516    /// **Errors:**
517    ///
518    /// Signing will fail if some required signers are not provided in
519    /// ``keypairs``; or, if the transaction has previously been partially signed,
520    /// some of the remaining required signers are not provided in ``keypairs``.
521    /// In other words, the transaction must be fully signed as a result of
522    /// calling this function.
523    ///
524    /// Signing will fail for any of the reasons described in the documentation
525    /// for :meth:`Transaction.partial_sign`.
526    ///
527    /// Args:
528    ///     keypairs (Sequence[Keypair | Presigner]): The signers for the transaction.
529    ///     recent_blockhash (Hash): The id of a recent ledger entry.
530    ///
531    pub fn sign(&mut self, keypairs: Vec<Signer>, recent_blockhash: SolderHash) -> PyResult<()> {
532        handle_py_err(
533            self.0
534                .try_sign(&SignerVec(keypairs), recent_blockhash.into()),
535        )
536    }
537
538    /// Sign the transaction with a subset of required keys, returning any errors.
539    ///
540    /// Unlike :meth:`Transaction.sign`, this method does not require all
541    /// keypairs to be provided, allowing a transaction to be signed in multiple
542    /// steps.
543    ///
544    /// It is permitted to sign a transaction with the same keypair multiple
545    /// times.
546    ///
547    /// If ``recent_blockhash`` is different than recorded in the transaction message's
548    /// ``recent_blockhash`` field, then the message's ``recent_blockhash`` will be updated
549    /// to the provided ``recent_blockhash``, and any prior signatures will be cleared.
550    ///
551    /// **Errors:**
552    ///
553    /// Signing will fail if
554    ///
555    /// - The transaction's :class:`~solders.message.Message` is malformed such that the number of
556    ///   required signatures recorded in its header
557    ///   (``num_required_signatures``) is greater than the length of its
558    ///   account keys (``account_keys``).
559    /// - Any of the provided signers in ``keypairs`` is not a required signer of
560    ///   the message.
561    /// - Any of the signers is a :class:`~solders.presigner.Presigner`, and its provided signature is
562    ///   incorrect.
563    ///
564    /// Args:
565    ///     keypairs (Sequence[Keypair | Presigner]): The signers for the transaction.
566    ///     recent_blockhash (Hash): The id of a recent ledger entry.
567    ///     
568    pub fn partial_sign(
569        &mut self,
570        keypairs: Vec<Signer>,
571        recent_blockhash: SolderHash,
572    ) -> PyResult<()> {
573        handle_py_err(
574            self.0
575                .try_partial_sign(&SignerVec(keypairs), recent_blockhash.into()),
576        )
577    }
578
579    /// Verifies that all signers have signed the message.
580    ///
581    /// Raises:
582    ///     TransactionError: if the check fails.
583    pub fn verify(&self) -> PyResult<()> {
584        handle_py_err(self.0.verify())
585    }
586
587    /// Verify the transaction and hash its message.
588    ///
589    /// Returns:
590    ///     Hash: The blake3 hash of the message.
591    ///
592    /// Raises:
593    ///     TransactionError: if the check fails.
594    pub fn verify_and_hash_message(&self) -> PyResult<SolderHash> {
595        handle_py_err(self.0.verify_and_hash_message())
596    }
597
598    /// Verifies that all signers have signed the message.
599    ///
600    /// Returns:
601    ///     list[bool]: a list with the length of required signatures, where each element is either ``True`` if that signer has signed, or ``False`` if not.
602    ///
603    pub fn verify_with_results(&self) -> Vec<bool> {
604        self.0.verify_with_results()
605    }
606
607    /// Get the positions of the pubkeys in account_keys associated with signing keypairs.
608    ///
609    /// Args:
610    ///     pubkeys (Sequence[Pubkey]): The pubkeys to find.
611    ///     
612    ///     Returns:
613    ///         list[Optional[int]]: The pubkey positions.
614    ///
615    pub fn get_signing_keypair_positions(
616        &self,
617        pubkeys: Vec<Pubkey>,
618    ) -> PyResult<Vec<Option<usize>>> {
619        let converted_pubkeys: Vec<PubkeyOriginal> =
620            pubkeys.into_iter().map(PubkeyOriginal::from).collect();
621        handle_py_err(self.0.get_signing_keypair_positions(&converted_pubkeys))
622    }
623
624    /// Replace all the signatures and pubkeys.
625    ///
626    /// Args:
627    ///     signers (Sequence[Tuple[Pubkey, Signature]]): The replacement pubkeys and signatures.
628    ///
629    pub fn replace_signatures(&mut self, signers: Vec<(Pubkey, Signature)>) -> PyResult<()> {
630        let converted_signers: Vec<(PubkeyOriginal, SignatureOriginal)> = signers
631            .into_iter()
632            .map(|(pubkey, signature)| {
633                (
634                    PubkeyOriginal::from(pubkey),
635                    SignatureOriginal::from(signature),
636                )
637            })
638            .collect();
639        handle_py_err(self.0.replace_signatures(&converted_signers))
640    }
641
642    /// Check if the transaction has been signed.
643    ///
644    /// Returns:
645    ///     bool: True if the transaction has been signed.
646    ///
647    pub fn is_signed(&self) -> bool {
648        self.0.is_signed()
649    }
650
651    /// See https://docs.rs/solana-sdk/latest/solana_sdk/transaction/fn.uses_durable_nonce.html
652    pub fn uses_durable_nonce(&self) -> Option<CompiledInstruction> {
653        uses_durable_nonce(&self.0).map(|x| CompiledInstruction::from(x.clone()))
654    }
655
656    /// Sanity checks the Transaction properties.
657    pub fn sanitize(&self) -> PyResult<()> {
658        handle_py_err(self.0.sanitize())
659    }
660
661    #[staticmethod]
662    #[pyo3(name = "default")]
663    /// Return a new default transaction.
664    ///
665    /// Returns:
666    ///     Transaction: The default transaction.
667    pub fn new_default() -> Self {
668        Self::default()
669    }
670
671    #[staticmethod]
672    /// Deserialize a serialized ``Transaction`` object.
673    ///
674    /// Args:
675    ///     data (bytes): the serialized ``Transaction``.
676    ///
677    /// Returns:
678    ///     Transaction: the deserialized ``Transaction``.
679    ///
680    /// Example:
681    ///     >>> from solders.transaction import Transaction
682    ///     >>> tx = Transaction.default()
683    ///     >>> assert Transaction.from_bytes(bytes(tx)) == tx
684    ///
685    pub fn from_bytes(data: &[u8]) -> PyResult<Self> {
686        Self::py_from_bytes(data)
687    }
688
689    /// Deprecated in the Solana Rust SDK, expose here only for testing.
690    pub fn get_nonce_pubkey_from_instruction(&self, ix: &CompiledInstruction) -> Option<Pubkey> {
691        get_nonce_pubkey_from_instruction(ix.as_ref(), self.as_ref()).map(Pubkey::from)
692    }
693}
694
695impl RichcmpEqualityOnly for Transaction {}
696pybytes_general_via_bincode!(Transaction);
697py_from_bytes_general_via_bincode!(Transaction);
698impl_display!(Transaction);
699solders_traits_core::common_methods_default!(Transaction);
700
701impl AsRef<TransactionOriginal> for Transaction {
702    fn as_ref(&self) -> &TransactionOriginal {
703        &self.0
704    }
705}
706
707/// Transaction version type that serializes to the string "legacy"
708#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
709#[serde(rename_all = "camelCase")]
710#[pyclass(module = "solders.transaction")]
711pub enum Legacy {
712    Legacy,
713}
714
715impl RichcmpEqualityOnly for Legacy {}
716
717#[pymethods]
718impl Legacy {
719    fn __richcmp__(
720        &self,
721        other: &Self,
722        op: pyo3::basic::CompareOp,
723    ) -> pyo3::prelude::PyResult<bool> {
724        // we override the default impl since it implicitly casts to in which causes problems when transaction
725        // version is represented as `Legacy | int`.
726        solders_traits_core::RichcmpEqualityOnly::richcmp(self, other, op)
727    }
728}
729
730impl From<Legacy> for LegacyOriginal {
731    fn from(x: Legacy) -> Self {
732        match x {
733            Legacy::Legacy => Self::Legacy,
734        }
735    }
736}
737
738impl From<LegacyOriginal> for Legacy {
739    fn from(x: LegacyOriginal) -> Self {
740        match x {
741            LegacyOriginal::Legacy => Self::Legacy,
742        }
743    }
744}
745
746#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromPyObject, EnumIntoPy)]
747#[serde(rename_all = "camelCase", untagged)]
748pub enum TransactionVersion {
749    Legacy(Legacy),
750    Number(u8),
751}
752
753impl From<TransactionVersion> for TransactionVersionOriginal {
754    fn from(v: TransactionVersion) -> Self {
755        match v {
756            TransactionVersion::Legacy(x) => Self::Legacy(x.into()),
757            TransactionVersion::Number(n) => Self::Number(n),
758        }
759    }
760}
761
762impl From<TransactionVersionOriginal> for TransactionVersion {
763    fn from(v: TransactionVersionOriginal) -> Self {
764        match v {
765            TransactionVersionOriginal::Legacy(x) => Self::Legacy(x.into()),
766            TransactionVersionOriginal::Number(n) => Self::Number(n),
767        }
768    }
769}