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}