Skip to main content

aptos_sdk/transaction/
types.rs

1//! Transaction types.
2
3use crate::error::AptosResult;
4use crate::transaction::authenticator::TransactionAuthenticator;
5use crate::transaction::payload::TransactionPayload;
6use crate::types::{AccountAddress, ChainId, HashValue};
7use serde::{Deserialize, Serialize};
8
9/// The raw transaction that a client signs.
10///
11/// A `RawTransaction` contains all the details of a transaction before
12/// it is signed, including the sender, payload, gas parameters, and
13/// expiration time.
14#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
15pub struct RawTransaction {
16    /// Sender's address.
17    pub sender: AccountAddress,
18    /// Sequence number of this transaction.
19    pub sequence_number: u64,
20    /// The transaction payload (entry function, script, etc.).
21    pub payload: TransactionPayload,
22    /// Maximum gas units the sender is willing to pay.
23    pub max_gas_amount: u64,
24    /// Price per gas unit in octas.
25    pub gas_unit_price: u64,
26    /// Expiration time in seconds since Unix epoch.
27    pub expiration_timestamp_secs: u64,
28    /// Chain ID to prevent cross-chain replay.
29    pub chain_id: ChainId,
30}
31
32impl RawTransaction {
33    /// Creates a new raw transaction.
34    pub fn new(
35        sender: AccountAddress,
36        sequence_number: u64,
37        payload: TransactionPayload,
38        max_gas_amount: u64,
39        gas_unit_price: u64,
40        expiration_timestamp_secs: u64,
41        chain_id: ChainId,
42    ) -> Self {
43        Self {
44            sender,
45            sequence_number,
46            payload,
47            max_gas_amount,
48            gas_unit_price,
49            expiration_timestamp_secs,
50            chain_id,
51        }
52    }
53
54    /// Generates the signing message for this transaction.
55    ///
56    /// This is the message that should be signed to create a valid
57    /// transaction authenticator.
58    ///
59    /// # Errors
60    ///
61    /// Returns an error if BCS serialization of the transaction fails.
62    pub fn signing_message(&self) -> AptosResult<Vec<u8>> {
63        let prefix = crate::crypto::sha3_256(b"APTOS::RawTransaction");
64        let bcs_bytes = aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)?;
65
66        let mut message = Vec::with_capacity(prefix.len() + bcs_bytes.len());
67        message.extend_from_slice(&prefix);
68        message.extend_from_slice(&bcs_bytes);
69        Ok(message)
70    }
71
72    /// Serializes this transaction to BCS bytes.
73    ///
74    /// # Errors
75    ///
76    /// Returns an error if BCS serialization fails.
77    pub fn to_bcs(&self) -> AptosResult<Vec<u8>> {
78        aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)
79    }
80}
81
82/// An orderless transaction using hash-based replay protection.
83///
84/// Unlike standard transactions that use sequence numbers, orderless transactions
85/// use a random nonce and a short expiration window (typically 60 seconds) to
86/// prevent replay attacks. This allows transactions to be submitted in any order.
87///
88/// # Note
89///
90/// Orderless transactions must be configured with a very short expiration time
91/// (recommended: 60 seconds or less) since the replay protection relies on
92/// the chain remembering recently seen transaction hashes.
93#[deprecated(
94    since = "0.6.0",
95    note = "This is a non-standard, homegrown orderless format (32-byte nonce + `APTOS::RawTransactionOrderless` \
96            domain separator) that the Aptos fullnode does NOT accept. Use the chain-compatible orderless \
97            support instead: `TransactionPayload::into_orderless` or \
98            `Aptos::{build_orderless_transaction, sign_and_submit_orderless, sign_submit_and_wait_orderless}`."
99)]
100#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
101pub struct RawTransactionOrderless {
102    /// Sender's address.
103    pub sender: AccountAddress,
104    /// Random nonce for uniqueness (32 bytes recommended).
105    pub nonce: Vec<u8>,
106    /// The transaction payload (entry function, script, etc.).
107    pub payload: TransactionPayload,
108    /// Maximum gas units the sender is willing to pay.
109    pub max_gas_amount: u64,
110    /// Price per gas unit in octas.
111    pub gas_unit_price: u64,
112    /// Expiration time in seconds since Unix epoch.
113    /// Should be short (e.g., current time + 60 seconds).
114    pub expiration_timestamp_secs: u64,
115    /// Chain ID to prevent cross-chain replay.
116    pub chain_id: ChainId,
117}
118
119#[allow(deprecated)] // inherent impl of a deprecated (kept-for-compat) type
120impl RawTransactionOrderless {
121    /// Creates a new orderless transaction with a random nonce.
122    pub fn new(
123        sender: AccountAddress,
124        payload: TransactionPayload,
125        max_gas_amount: u64,
126        gas_unit_price: u64,
127        expiration_timestamp_secs: u64,
128        chain_id: ChainId,
129    ) -> Self {
130        // Generate a random 32-byte nonce
131        let mut nonce = vec![0u8; 32];
132        rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut nonce);
133        Self {
134            sender,
135            nonce,
136            payload,
137            max_gas_amount,
138            gas_unit_price,
139            expiration_timestamp_secs,
140            chain_id,
141        }
142    }
143
144    /// Creates a new orderless transaction with a specific nonce.
145    pub fn with_nonce(
146        sender: AccountAddress,
147        nonce: Vec<u8>,
148        payload: TransactionPayload,
149        max_gas_amount: u64,
150        gas_unit_price: u64,
151        expiration_timestamp_secs: u64,
152        chain_id: ChainId,
153    ) -> Self {
154        Self {
155            sender,
156            nonce,
157            payload,
158            max_gas_amount,
159            gas_unit_price,
160            expiration_timestamp_secs,
161            chain_id,
162        }
163    }
164
165    /// Generates the signing message for this orderless transaction.
166    ///
167    /// # Errors
168    ///
169    /// Returns an error if BCS serialization of the transaction fails.
170    pub fn signing_message(&self) -> AptosResult<Vec<u8>> {
171        let prefix = crate::crypto::sha3_256(b"APTOS::RawTransactionOrderless");
172        let bcs_bytes = aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)?;
173
174        let mut message = Vec::with_capacity(prefix.len() + bcs_bytes.len());
175        message.extend_from_slice(&prefix);
176        message.extend_from_slice(&bcs_bytes);
177        Ok(message)
178    }
179
180    /// Serializes this transaction to BCS bytes.
181    ///
182    /// # Errors
183    ///
184    /// Returns an error if BCS serialization fails.
185    pub fn to_bcs(&self) -> AptosResult<Vec<u8>> {
186        aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)
187    }
188}
189
190/// A signed orderless transaction ready for submission.
191#[deprecated(
192    since = "0.6.0",
193    note = "This wraps the non-standard `RawTransactionOrderless` and is NOT accepted by the Aptos fullnode. \
194            Use the chain-compatible orderless support instead: `TransactionPayload::into_orderless` or \
195            `Aptos::{build_orderless_transaction, sign_and_submit_orderless, sign_submit_and_wait_orderless}`."
196)]
197#[allow(deprecated)] // the `raw_txn` field references the deprecated `RawTransactionOrderless`
198#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
199pub struct SignedTransactionOrderless {
200    /// The orderless raw transaction.
201    pub raw_txn: RawTransactionOrderless,
202    /// The authenticator (signature(s) and public key(s)).
203    pub authenticator: TransactionAuthenticator,
204}
205
206#[allow(deprecated)] // inherent impl of a deprecated (kept-for-compat) type
207impl SignedTransactionOrderless {
208    /// Creates a new signed orderless transaction.
209    pub fn new(raw_txn: RawTransactionOrderless, authenticator: TransactionAuthenticator) -> Self {
210        Self {
211            raw_txn,
212            authenticator,
213        }
214    }
215
216    /// Serializes this signed transaction to BCS bytes.
217    ///
218    /// # Errors
219    ///
220    /// Returns an error if BCS serialization fails.
221    pub fn to_bcs(&self) -> AptosResult<Vec<u8>> {
222        aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)
223    }
224
225    /// Returns the sender address.
226    pub fn sender(&self) -> AccountAddress {
227        self.raw_txn.sender
228    }
229
230    /// Returns the nonce.
231    pub fn nonce(&self) -> &[u8] {
232        &self.raw_txn.nonce
233    }
234
235    /// Computes the transaction hash.
236    ///
237    /// # Errors
238    ///
239    /// Returns an error if BCS serialization of the transaction fails.
240    pub fn hash(&self) -> AptosResult<HashValue> {
241        let bcs_bytes = self.to_bcs()?;
242        let prefix = crate::crypto::sha3_256(b"APTOS::Transaction");
243
244        let mut data = Vec::with_capacity(prefix.len() + 1 + bcs_bytes.len());
245        data.extend_from_slice(&prefix);
246        // Use variant 2 for orderless user transactions (assuming standard is 0, script is 1)
247        data.push(2);
248        data.extend_from_slice(&bcs_bytes);
249
250        Ok(HashValue::new(crate::crypto::sha3_256(&data)))
251    }
252}
253
254/// A signed transaction ready for submission.
255#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
256pub struct SignedTransaction {
257    /// The raw transaction.
258    pub raw_txn: RawTransaction,
259    /// The authenticator (signature(s) and public key(s)).
260    pub authenticator: TransactionAuthenticator,
261}
262
263impl SignedTransaction {
264    /// Creates a new signed transaction.
265    pub fn new(raw_txn: RawTransaction, authenticator: TransactionAuthenticator) -> Self {
266        Self {
267            raw_txn,
268            authenticator,
269        }
270    }
271
272    /// Clones this transaction with authenticators rewritten for `/transactions/simulate`.
273    ///
274    /// The Aptos fullnode rejects simulate requests that carry a cryptographically **valid**
275    /// signature. [`crate::api::FullnodeClient::simulate_transaction`] applies this
276    /// transformation automatically; this helper exists for callers who serialize manually.
277    #[must_use]
278    pub fn for_simulate_endpoint(&self) -> Self {
279        Self {
280            raw_txn: self.raw_txn.clone(),
281            authenticator: self.authenticator.clone().for_simulate_endpoint(),
282        }
283    }
284
285    /// Serializes this signed transaction to BCS bytes.
286    ///
287    /// # Errors
288    ///
289    /// Returns an error if BCS serialization fails.
290    pub fn to_bcs(&self) -> AptosResult<Vec<u8>> {
291        aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)
292    }
293
294    /// Returns the sender address.
295    pub fn sender(&self) -> AccountAddress {
296        self.raw_txn.sender
297    }
298
299    /// Returns the sequence number.
300    pub fn sequence_number(&self) -> u64 {
301        self.raw_txn.sequence_number
302    }
303
304    /// Computes the transaction hash.
305    ///
306    /// # Errors
307    ///
308    /// Returns an error if BCS serialization of the transaction fails.
309    pub fn hash(&self) -> AptosResult<HashValue> {
310        let bcs_bytes = self.to_bcs()?;
311        let prefix = crate::crypto::sha3_256(b"APTOS::Transaction");
312
313        let mut data = Vec::with_capacity(prefix.len() + 1 + bcs_bytes.len());
314        data.extend_from_slice(&prefix);
315        data.push(0); // User transaction variant
316        data.extend_from_slice(&bcs_bytes);
317
318        Ok(HashValue::new(crate::crypto::sha3_256(&data)))
319    }
320
321    /// Verifies the transaction authenticator against the transaction's
322    /// signing message and sender address.
323    ///
324    /// # Errors
325    ///
326    /// Returns an error if the authenticator does not verify or if the
327    /// derived sender address does not match the raw transaction sender.
328    pub fn verify_signature(&self) -> AptosResult<()> {
329        match &self.authenticator {
330            #[cfg(feature = "ed25519")]
331            TransactionAuthenticator::Ed25519 {
332                public_key,
333                signature,
334            } => {
335                let public_key = crate::crypto::Ed25519PublicKey::from_bytes(&public_key.0)?;
336                let signature = crate::crypto::Ed25519Signature::from_bytes(&signature.0)?;
337                let signing_message = self.raw_txn.signing_message()?;
338                public_key.verify(&signing_message, &signature)?;
339                if public_key.to_address() != self.raw_txn.sender {
340                    return Err(crate::error::AptosError::InvalidSignature(
341                        "signed transaction sender does not match authenticator address".into(),
342                    ));
343                }
344                Ok(())
345            }
346            #[cfg(not(feature = "ed25519"))]
347            TransactionAuthenticator::Ed25519 { .. } => Err(
348                crate::error::AptosError::FeatureNotEnabled("Ed25519 verification".into()),
349            ),
350            #[cfg(feature = "ed25519")]
351            TransactionAuthenticator::MultiEd25519 {
352                public_key,
353                signature,
354            } => {
355                let public_key = crate::crypto::MultiEd25519PublicKey::from_bytes(public_key)?;
356                let signature = crate::crypto::MultiEd25519Signature::from_bytes(signature)?;
357                let signing_message = self.raw_txn.signing_message()?;
358                public_key.verify(&signing_message, &signature)?;
359                if public_key.to_address() != self.raw_txn.sender {
360                    return Err(crate::error::AptosError::InvalidSignature(
361                        "signed transaction sender does not match authenticator address".into(),
362                    ));
363                }
364                Ok(())
365            }
366            #[cfg(not(feature = "ed25519"))]
367            TransactionAuthenticator::MultiEd25519 { .. } => Err(
368                crate::error::AptosError::FeatureNotEnabled("MultiEd25519 verification".into()),
369            ),
370            TransactionAuthenticator::SingleSender { sender } => {
371                let signing_message = self.raw_txn.signing_message()?;
372                sender.verify(&signing_message)?;
373                if sender.derived_address()? != self.raw_txn.sender {
374                    return Err(crate::error::AptosError::InvalidSignature(
375                        "signed transaction sender does not match authenticator address".into(),
376                    ));
377                }
378                Ok(())
379            }
380            TransactionAuthenticator::MultiAgent {
381                sender,
382                secondary_signer_addresses,
383                secondary_signers,
384            } => {
385                if secondary_signer_addresses.len() != secondary_signers.len() {
386                    return Err(crate::error::AptosError::InvalidSignature(
387                        "secondary signer count does not match secondary signer addresses".into(),
388                    ));
389                }
390                let signing_message = MultiAgentRawTransaction::new(
391                    self.raw_txn.clone(),
392                    secondary_signer_addresses.clone(),
393                )
394                .signing_message()?;
395                sender.verify(&signing_message)?;
396                if sender.derived_address()? != self.raw_txn.sender {
397                    return Err(crate::error::AptosError::InvalidSignature(
398                        "signed transaction sender does not match authenticator address".into(),
399                    ));
400                }
401                for (expected_address, signer) in secondary_signer_addresses
402                    .iter()
403                    .zip(secondary_signers.iter())
404                {
405                    signer.verify(&signing_message)?;
406                    if signer.derived_address()? != *expected_address {
407                        return Err(crate::error::AptosError::InvalidSignature(
408                            "secondary signer address does not match authenticator address".into(),
409                        ));
410                    }
411                }
412                Ok(())
413            }
414            TransactionAuthenticator::FeePayer {
415                sender,
416                secondary_signer_addresses,
417                secondary_signers,
418                fee_payer_address,
419                fee_payer_signer,
420            } => {
421                if secondary_signer_addresses.len() != secondary_signers.len() {
422                    return Err(crate::error::AptosError::InvalidSignature(
423                        "secondary signer count does not match secondary signer addresses".into(),
424                    ));
425                }
426                let signing_message = FeePayerRawTransaction::new(
427                    self.raw_txn.clone(),
428                    secondary_signer_addresses.clone(),
429                    *fee_payer_address,
430                )
431                .signing_message()?;
432                sender.verify(&signing_message)?;
433                if sender.derived_address()? != self.raw_txn.sender {
434                    return Err(crate::error::AptosError::InvalidSignature(
435                        "signed transaction sender does not match authenticator address".into(),
436                    ));
437                }
438                for (expected_address, signer) in secondary_signer_addresses
439                    .iter()
440                    .zip(secondary_signers.iter())
441                {
442                    signer.verify(&signing_message)?;
443                    if signer.derived_address()? != *expected_address {
444                        return Err(crate::error::AptosError::InvalidSignature(
445                            "secondary signer address does not match authenticator address".into(),
446                        ));
447                    }
448                }
449                fee_payer_signer.verify(&signing_message)?;
450                if fee_payer_signer.derived_address()? != *fee_payer_address {
451                    return Err(crate::error::AptosError::InvalidSignature(
452                        "fee payer address does not match authenticator address".into(),
453                    ));
454                }
455                Ok(())
456            }
457        }
458    }
459}
460
461/// Information about a submitted/executed transaction.
462#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
463pub struct TransactionInfo {
464    /// The transaction hash.
465    pub hash: HashValue,
466    /// The ledger version this transaction was committed at.
467    #[serde(default)]
468    pub version: Option<u64>,
469    /// Whether the transaction succeeded.
470    #[serde(default)]
471    pub success: Option<bool>,
472    /// The VM status message.
473    #[serde(default)]
474    pub vm_status: Option<String>,
475    /// Gas used by the transaction.
476    #[serde(default)]
477    pub gas_used: Option<u64>,
478}
479
480impl TransactionInfo {
481    /// Returns true if the transaction succeeded.
482    pub fn is_success(&self) -> bool {
483        self.success.unwrap_or(false)
484    }
485}
486
487/// Multi-agent transaction with additional signers.
488#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
489pub struct MultiAgentRawTransaction {
490    /// The raw transaction.
491    pub raw_txn: RawTransaction,
492    /// Secondary signer addresses.
493    pub secondary_signer_addresses: Vec<AccountAddress>,
494}
495
496impl MultiAgentRawTransaction {
497    /// Creates a new multi-agent transaction.
498    pub fn new(raw_txn: RawTransaction, secondary_signer_addresses: Vec<AccountAddress>) -> Self {
499        Self {
500            raw_txn,
501            secondary_signer_addresses,
502        }
503    }
504
505    /// Generates the signing message for multi-agent transactions.
506    ///
507    /// # Errors
508    ///
509    /// Returns an error if BCS serialization fails.
510    pub fn signing_message(&self) -> AptosResult<Vec<u8>> {
511        // Serialize as RawTransactionWithData::MultiAgent variant
512        #[derive(Serialize)]
513        enum RawTransactionWithData<'a> {
514            MultiAgent {
515                raw_txn: &'a RawTransaction,
516                secondary_signer_addresses: &'a Vec<AccountAddress>,
517            },
518        }
519
520        let prefix = crate::crypto::sha3_256(b"APTOS::RawTransactionWithData");
521
522        let data = RawTransactionWithData::MultiAgent {
523            raw_txn: &self.raw_txn,
524            secondary_signer_addresses: &self.secondary_signer_addresses,
525        };
526
527        let bcs_bytes = aptos_bcs::to_bytes(&data).map_err(crate::error::AptosError::bcs)?;
528
529        let mut message = Vec::with_capacity(prefix.len() + bcs_bytes.len());
530        message.extend_from_slice(&prefix);
531        message.extend_from_slice(&bcs_bytes);
532        Ok(message)
533    }
534}
535
536/// Fee payer transaction where a third party pays gas fees.
537#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
538pub struct FeePayerRawTransaction {
539    /// The raw transaction.
540    pub raw_txn: RawTransaction,
541    /// Secondary signer addresses (for multi-agent).
542    pub secondary_signer_addresses: Vec<AccountAddress>,
543    /// The fee payer's address.
544    pub fee_payer_address: AccountAddress,
545}
546
547impl FeePayerRawTransaction {
548    /// Creates a new fee payer transaction.
549    pub fn new(
550        raw_txn: RawTransaction,
551        secondary_signer_addresses: Vec<AccountAddress>,
552        fee_payer_address: AccountAddress,
553    ) -> Self {
554        Self {
555            raw_txn,
556            secondary_signer_addresses,
557            fee_payer_address,
558        }
559    }
560
561    /// Creates a fee payer transaction without secondary signers.
562    pub fn new_simple(raw_txn: RawTransaction, fee_payer_address: AccountAddress) -> Self {
563        Self {
564            raw_txn,
565            secondary_signer_addresses: vec![],
566            fee_payer_address,
567        }
568    }
569
570    /// Generates the signing message for fee payer transactions.
571    ///
572    /// # Errors
573    ///
574    /// Returns an error if BCS serialization fails.
575    pub fn signing_message(&self) -> AptosResult<Vec<u8>> {
576        #[derive(Serialize)]
577        enum RawTransactionWithData<'a> {
578            #[allow(dead_code)]
579            MultiAgent {
580                raw_txn: &'a RawTransaction,
581                secondary_signer_addresses: &'a Vec<AccountAddress>,
582            },
583            MultiAgentWithFeePayer {
584                raw_txn: &'a RawTransaction,
585                secondary_signer_addresses: &'a Vec<AccountAddress>,
586                fee_payer_address: &'a AccountAddress,
587            },
588        }
589        let prefix = crate::crypto::sha3_256(b"APTOS::RawTransactionWithData");
590
591        let data = RawTransactionWithData::MultiAgentWithFeePayer {
592            raw_txn: &self.raw_txn,
593            secondary_signer_addresses: &self.secondary_signer_addresses,
594            fee_payer_address: &self.fee_payer_address,
595        };
596
597        let bcs_bytes = aptos_bcs::to_bytes(&data).map_err(crate::error::AptosError::bcs)?;
598
599        let mut message = Vec::with_capacity(prefix.len() + bcs_bytes.len());
600        message.extend_from_slice(&prefix);
601        message.extend_from_slice(&bcs_bytes);
602        Ok(message)
603    }
604}
605
606#[cfg(test)]
607mod tests {
608    use super::*;
609    use crate::transaction::payload::EntryFunction;
610    use crate::types::MoveModuleId;
611
612    fn create_test_raw_transaction() -> RawTransaction {
613        RawTransaction::new(
614            AccountAddress::ONE,
615            0,
616            TransactionPayload::EntryFunction(EntryFunction {
617                module: MoveModuleId::from_str_strict("0x1::coin").unwrap(),
618                function: "transfer".to_string(),
619                type_args: vec![],
620                args: vec![],
621            }),
622            100_000,
623            100,
624            1_000_000_000,
625            ChainId::testnet(),
626        )
627    }
628
629    #[cfg(feature = "ed25519")]
630    fn create_transfer_raw_transaction(sender: AccountAddress) -> RawTransaction {
631        RawTransaction::new(
632            sender,
633            0,
634            EntryFunction::apt_transfer(AccountAddress::from_hex("0x2").unwrap(), 1)
635                .unwrap()
636                .into(),
637            100_000,
638            100,
639            1_000_000_000,
640            ChainId::testnet(),
641        )
642    }
643
644    #[test]
645    fn test_raw_transaction_signing_message() {
646        let txn = create_test_raw_transaction();
647        let message = txn.signing_message().unwrap();
648        assert!(!message.is_empty());
649        // First 32 bytes should be the hash prefix
650        assert_eq!(message.len(), 32 + txn.to_bcs().unwrap().len());
651    }
652
653    #[test]
654    fn test_raw_transaction_fields() {
655        let txn = create_test_raw_transaction();
656        assert_eq!(txn.sender, AccountAddress::ONE);
657        assert_eq!(txn.sequence_number, 0);
658        assert_eq!(txn.max_gas_amount, 100_000);
659        assert_eq!(txn.gas_unit_price, 100);
660        assert_eq!(txn.expiration_timestamp_secs, 1_000_000_000);
661        assert_eq!(txn.chain_id, ChainId::testnet());
662    }
663
664    #[test]
665    fn test_raw_transaction_bcs_serialization() {
666        let txn = create_test_raw_transaction();
667        let bcs = txn.to_bcs().unwrap();
668        assert!(!bcs.is_empty());
669    }
670
671    #[test]
672    fn test_signed_transaction() {
673        use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
674        let txn = create_test_raw_transaction();
675        // Create a dummy authenticator
676        let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
677            public_key: Ed25519PublicKey([0u8; 32]),
678            signature: Ed25519Signature([0u8; 64]),
679        };
680        let signed = SignedTransaction::new(txn, auth);
681        assert_eq!(signed.sender(), AccountAddress::ONE);
682    }
683
684    #[test]
685    fn test_signed_transaction_bcs() {
686        use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
687        let txn = create_test_raw_transaction();
688        let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
689            public_key: Ed25519PublicKey([0u8; 32]),
690            signature: Ed25519Signature([0u8; 64]),
691        };
692        let signed = SignedTransaction::new(txn, auth);
693        let bcs = signed.to_bcs().unwrap();
694        assert!(!bcs.is_empty());
695    }
696
697    #[test]
698    fn test_authenticator_bcs_format() {
699        // Test that Ed25519 authenticator serializes WITH length prefixes (Aptos BCS format)
700        use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
701        let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
702            public_key: Ed25519PublicKey([0xab; 32]),
703            signature: Ed25519Signature([0xcd; 64]),
704        };
705        let bcs = aptos_bcs::to_bytes(&auth).unwrap();
706
707        // Print for debugging
708        println!("Authenticator BCS bytes: {}", const_hex::encode(&bcs));
709        println!("First byte (variant index): {}", bcs[0]);
710        println!("Second byte (length prefix): {}", bcs[1]);
711        println!("Third byte (first pubkey byte): {}", bcs[2]);
712
713        // Ed25519 variant should be index 0
714        assert_eq!(bcs[0], 0, "Ed25519 variant index should be 0");
715        // Next byte is length prefix for pubkey (32 = 0x20)
716        assert_eq!(bcs[1], 32, "Pubkey length prefix should be 32");
717        // Next 32 bytes should be pubkey
718        assert_eq!(bcs[2], 0xab, "First pubkey byte should be 0xab");
719        // After pubkey (1 + 1 + 32 = 34), length prefix for signature (64 = 0x40)
720        assert_eq!(bcs[34], 64, "Signature length prefix should be 64");
721        // Signature starts at offset 35
722        assert_eq!(bcs[35], 0xcd, "First signature byte should be 0xcd");
723        // Total: 1 (variant) + 1 (pubkey len) + 32 (pubkey) + 1 (sig len) + 64 (sig) = 99
724        assert_eq!(bcs.len(), 99, "BCS length should be 99");
725    }
726
727    #[test]
728    fn test_transaction_info_deserialization() {
729        let json = r#"{
730            "version": 12345,
731            "hash": "0x0000000000000000000000000000000000000000000000000000000000000001",
732            "gas_used": 100,
733            "success": true,
734            "vm_status": "Executed successfully"
735        }"#;
736        let info: TransactionInfo = serde_json::from_str(json).unwrap();
737        assert_eq!(info.version, Some(12345));
738        assert_eq!(info.gas_used, Some(100));
739        assert_eq!(info.success, Some(true));
740        assert_eq!(info.vm_status, Some("Executed successfully".to_string()));
741    }
742
743    #[test]
744    fn test_fee_payer_raw_transaction_new() {
745        let raw_txn = create_test_raw_transaction();
746        let secondary_addr = AccountAddress::from_hex("0x2").unwrap();
747        let fee_payer_addr = AccountAddress::THREE;
748        let fee_payer = FeePayerRawTransaction::new(raw_txn, vec![secondary_addr], fee_payer_addr);
749        assert_eq!(fee_payer.fee_payer_address, AccountAddress::THREE);
750        assert_eq!(fee_payer.secondary_signer_addresses.len(), 1);
751    }
752
753    #[test]
754    fn test_fee_payer_raw_transaction_new_simple() {
755        let raw_txn = create_test_raw_transaction();
756        let fee_payer = FeePayerRawTransaction::new_simple(raw_txn, AccountAddress::THREE);
757        assert_eq!(fee_payer.fee_payer_address, AccountAddress::THREE);
758        assert!(fee_payer.secondary_signer_addresses.is_empty());
759    }
760
761    #[test]
762    fn test_fee_payer_signing_message() {
763        let raw_txn = create_test_raw_transaction();
764        let fee_payer = FeePayerRawTransaction::new_simple(raw_txn, AccountAddress::THREE);
765        let message = fee_payer.signing_message().unwrap();
766        assert!(!message.is_empty());
767    }
768
769    #[test]
770    fn test_signed_transaction_hash() {
771        use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
772        let txn = create_test_raw_transaction();
773        let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
774            public_key: Ed25519PublicKey([0u8; 32]),
775            signature: Ed25519Signature([0u8; 64]),
776        };
777        let signed = SignedTransaction::new(txn, auth);
778        let hash = signed.hash().unwrap();
779        // Hash should be 32 bytes
780        assert_eq!(hash.as_bytes().len(), 32);
781        // Hash should be deterministic
782        let hash2 = signed.hash().unwrap();
783        assert_eq!(hash, hash2);
784    }
785
786    #[test]
787    fn test_signed_transaction_sequence_number() {
788        use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
789        let txn = create_test_raw_transaction();
790        let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
791            public_key: Ed25519PublicKey([0u8; 32]),
792            signature: Ed25519Signature([0u8; 64]),
793        };
794        let signed = SignedTransaction::new(txn, auth);
795        assert_eq!(signed.sequence_number(), 0);
796    }
797
798    #[test]
799    fn test_transaction_info_is_success() {
800        let info_success = TransactionInfo {
801            hash: HashValue::new([0; 32]),
802            version: Some(1),
803            success: Some(true),
804            vm_status: None,
805            gas_used: Some(100),
806        };
807        assert!(info_success.is_success());
808
809        let info_failed = TransactionInfo {
810            hash: HashValue::new([0; 32]),
811            version: Some(1),
812            success: Some(false),
813            vm_status: Some("Failed".to_string()),
814            gas_used: Some(100),
815        };
816        assert!(!info_failed.is_success());
817
818        let info_unknown = TransactionInfo {
819            hash: HashValue::new([0; 32]),
820            version: None,
821            success: None,
822            vm_status: None,
823            gas_used: None,
824        };
825        assert!(!info_unknown.is_success());
826    }
827
828    #[cfg(feature = "ed25519")]
829    #[test]
830    fn test_verify_signature_ed25519_sender_mismatch() {
831        use crate::account::Ed25519Account;
832        use crate::error::AptosError;
833        use crate::transaction::builder::sign_transaction;
834
835        let signer = Ed25519Account::generate();
836        let claimed_sender = Ed25519Account::generate();
837        let raw_txn = create_transfer_raw_transaction(claimed_sender.address());
838        let signed_txn = sign_transaction(&raw_txn, &signer).unwrap();
839        let err = signed_txn.verify_signature().unwrap_err();
840        assert!(
841            matches!(err, AptosError::InvalidSignature(msg) if msg.contains("sender does not match"))
842        );
843    }
844
845    #[cfg(feature = "ed25519")]
846    #[test]
847    fn test_verify_signature_multi_ed25519_sender_mismatch() {
848        use crate::account::Ed25519Account;
849        use crate::account::MultiEd25519Account;
850        use crate::crypto::Ed25519PrivateKey;
851        use crate::error::AptosError;
852        use crate::transaction::builder::sign_transaction;
853
854        let signer = MultiEd25519Account::new(
855            vec![Ed25519PrivateKey::generate(), Ed25519PrivateKey::generate()],
856            2,
857        )
858        .unwrap();
859        let claimed_sender = Ed25519Account::generate();
860        let raw_txn = create_transfer_raw_transaction(claimed_sender.address());
861        let signed_txn = sign_transaction(&raw_txn, &signer).unwrap();
862        let err = signed_txn.verify_signature().unwrap_err();
863        assert!(
864            matches!(err, AptosError::InvalidSignature(msg) if msg.contains("sender does not match"))
865        );
866    }
867
868    #[cfg(feature = "ed25519")]
869    #[test]
870    fn test_verify_signature_single_sender_sender_mismatch() {
871        use crate::account::Ed25519SingleKeyAccount;
872        use crate::error::AptosError;
873        use crate::transaction::builder::sign_transaction;
874
875        let signer = Ed25519SingleKeyAccount::generate();
876        let claimed_sender = Ed25519SingleKeyAccount::generate();
877        let raw_txn = create_transfer_raw_transaction(claimed_sender.address());
878        let signed_txn = sign_transaction(&raw_txn, &signer).unwrap();
879        let err = signed_txn.verify_signature().unwrap_err();
880        assert!(
881            matches!(err, AptosError::InvalidSignature(msg) if msg.contains("sender does not match"))
882        );
883    }
884
885    #[cfg(feature = "ed25519")]
886    #[test]
887    fn test_verify_signature_multi_agent_sender_mismatch() {
888        use crate::account::{Account, Ed25519Account};
889        use crate::error::AptosError;
890        use crate::transaction::builder::sign_multi_agent_transaction;
891
892        let signer = Ed25519Account::generate();
893        let claimed_sender = Ed25519Account::generate();
894        let secondary = Ed25519Account::generate();
895        let raw_txn = create_transfer_raw_transaction(claimed_sender.address());
896
897        let multi_agent = MultiAgentRawTransaction::new(raw_txn, vec![secondary.address()]);
898        let secondary_refs: Vec<&dyn Account> = vec![&secondary];
899        let signed_txn =
900            sign_multi_agent_transaction(&multi_agent, &signer, &secondary_refs).unwrap();
901        let err = signed_txn.verify_signature().unwrap_err();
902        assert!(
903            matches!(err, AptosError::InvalidSignature(msg) if msg.contains("sender does not match"))
904        );
905    }
906
907    #[cfg(feature = "ed25519")]
908    #[test]
909    fn test_verify_signature_multi_agent_secondary_address_mismatch() {
910        use crate::account::{Account, Ed25519Account};
911        use crate::error::AptosError;
912        use crate::transaction::builder::sign_multi_agent_transaction;
913
914        let sender = Ed25519Account::generate();
915        let expected_secondary = Ed25519Account::generate();
916        let wrong_secondary = Ed25519Account::generate();
917        let raw_txn = create_transfer_raw_transaction(sender.address());
918        let multi_agent =
919            MultiAgentRawTransaction::new(raw_txn, vec![expected_secondary.address()]);
920        let secondary_refs: Vec<&dyn Account> = vec![&wrong_secondary];
921        let signed = sign_multi_agent_transaction(&multi_agent, &sender, &secondary_refs).unwrap();
922        let err = signed.verify_signature().unwrap_err();
923        assert!(matches!(err, AptosError::InvalidSignature(msg) if msg
924            .contains("secondary signer address does not match")));
925    }
926
927    #[cfg(feature = "ed25519")]
928    #[test]
929    fn test_verify_signature_fee_payer_sender_mismatch() {
930        use crate::account::{Account, Ed25519Account};
931        use crate::error::AptosError;
932        use crate::transaction::builder::sign_fee_payer_transaction;
933
934        let signer = Ed25519Account::generate();
935        let claimed_sender = Ed25519Account::generate();
936        let secondary = Ed25519Account::generate();
937        let fee_payer = Ed25519Account::generate();
938        let raw_txn = create_transfer_raw_transaction(claimed_sender.address());
939        let fee_payer_txn =
940            FeePayerRawTransaction::new(raw_txn, vec![secondary.address()], fee_payer.address());
941        let secondary_refs: Vec<&dyn Account> = vec![&secondary];
942        let signed_txn =
943            sign_fee_payer_transaction(&fee_payer_txn, &signer, &secondary_refs, &fee_payer)
944                .unwrap();
945        let err = signed_txn.verify_signature().unwrap_err();
946        assert!(
947            matches!(err, AptosError::InvalidSignature(msg) if msg.contains("sender does not match"))
948        );
949    }
950
951    #[cfg(feature = "ed25519")]
952    #[test]
953    fn test_verify_signature_fee_payer_secondary_address_mismatch() {
954        use crate::account::{Account, Ed25519Account};
955        use crate::error::AptosError;
956        use crate::transaction::builder::sign_fee_payer_transaction;
957
958        let sender = Ed25519Account::generate();
959        let expected_secondary = Ed25519Account::generate();
960        let wrong_secondary = Ed25519Account::generate();
961        let fee_payer = Ed25519Account::generate();
962        let raw_txn = create_transfer_raw_transaction(sender.address());
963        let fee_payer_txn = FeePayerRawTransaction::new(
964            raw_txn,
965            vec![expected_secondary.address()],
966            fee_payer.address(),
967        );
968        let secondary_refs: Vec<&dyn Account> = vec![&wrong_secondary];
969        let signed =
970            sign_fee_payer_transaction(&fee_payer_txn, &sender, &secondary_refs, &fee_payer)
971                .unwrap();
972        let err = signed.verify_signature().unwrap_err();
973        assert!(matches!(err, AptosError::InvalidSignature(msg) if msg
974            .contains("secondary signer address does not match")));
975    }
976
977    #[cfg(feature = "ed25519")]
978    #[test]
979    fn test_verify_signature_fee_payer_address_mismatch() {
980        use crate::account::{Account, Ed25519Account};
981        use crate::error::AptosError;
982        use crate::transaction::authenticator::{AccountAuthenticator, TransactionAuthenticator};
983        use crate::transaction::builder::sign_fee_payer_transaction;
984
985        let sender = Ed25519Account::generate();
986        let secondary = Ed25519Account::generate();
987        let fee_payer = Ed25519Account::generate();
988        let impostor_fee_payer = Ed25519Account::generate();
989        let raw_txn = create_transfer_raw_transaction(sender.address());
990        let fee_payer_txn =
991            FeePayerRawTransaction::new(raw_txn, vec![secondary.address()], fee_payer.address());
992        let secondary_refs: Vec<&dyn Account> = vec![&secondary];
993        let mut signed =
994            sign_fee_payer_transaction(&fee_payer_txn, &sender, &secondary_refs, &fee_payer)
995                .unwrap();
996
997        let signing_message = FeePayerRawTransaction::new(
998            signed.raw_txn.clone(),
999            vec![secondary.address()],
1000            fee_payer.address(),
1001        )
1002        .signing_message()
1003        .unwrap();
1004        let impostor_auth = AccountAuthenticator::ed25519(
1005            impostor_fee_payer.public_key_bytes(),
1006            impostor_fee_payer.sign(&signing_message).unwrap(),
1007        );
1008        if let TransactionAuthenticator::FeePayer {
1009            fee_payer_signer, ..
1010        } = &mut signed.authenticator
1011        {
1012            *fee_payer_signer = impostor_auth;
1013        } else {
1014            panic!("expected FeePayer authenticator");
1015        }
1016        let err = signed.verify_signature().unwrap_err();
1017        assert!(
1018            matches!(err, AptosError::InvalidSignature(msg) if msg.contains("fee payer address"))
1019        );
1020    }
1021
1022    #[allow(deprecated)] // exercises the deprecated homegrown orderless types
1023    fn create_test_orderless_transaction() -> RawTransactionOrderless {
1024        RawTransactionOrderless::with_nonce(
1025            AccountAddress::ONE,
1026            vec![
1027                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
1028                24, 25, 26, 27, 28, 29, 30, 31, 32,
1029            ],
1030            TransactionPayload::EntryFunction(EntryFunction {
1031                module: MoveModuleId::from_str_strict("0x1::coin").unwrap(),
1032                function: "transfer".to_string(),
1033                type_args: vec![],
1034                args: vec![],
1035            }),
1036            100_000,
1037            100,
1038            1_000_000_000,
1039            ChainId::testnet(),
1040        )
1041    }
1042
1043    #[test]
1044    #[allow(deprecated)]
1045    fn test_orderless_transaction_with_nonce() {
1046        let nonce = vec![0xab; 32];
1047        let txn = RawTransactionOrderless::with_nonce(
1048            AccountAddress::ONE,
1049            nonce.clone(),
1050            TransactionPayload::EntryFunction(EntryFunction {
1051                module: MoveModuleId::from_str_strict("0x1::coin").unwrap(),
1052                function: "transfer".to_string(),
1053                type_args: vec![],
1054                args: vec![],
1055            }),
1056            100_000,
1057            100,
1058            1_000_000_000,
1059            ChainId::testnet(),
1060        );
1061        assert_eq!(txn.sender, AccountAddress::ONE);
1062        assert_eq!(txn.nonce, nonce);
1063        assert_eq!(txn.max_gas_amount, 100_000);
1064        assert_eq!(txn.gas_unit_price, 100);
1065    }
1066
1067    #[test]
1068    #[allow(deprecated)]
1069    fn test_orderless_transaction_new_generates_random_nonce() {
1070        let txn1 = RawTransactionOrderless::new(
1071            AccountAddress::ONE,
1072            TransactionPayload::EntryFunction(EntryFunction {
1073                module: MoveModuleId::from_str_strict("0x1::coin").unwrap(),
1074                function: "transfer".to_string(),
1075                type_args: vec![],
1076                args: vec![],
1077            }),
1078            100_000,
1079            100,
1080            1_000_000_000,
1081            ChainId::testnet(),
1082        );
1083        let txn2 = RawTransactionOrderless::new(
1084            AccountAddress::ONE,
1085            TransactionPayload::EntryFunction(EntryFunction {
1086                module: MoveModuleId::from_str_strict("0x1::coin").unwrap(),
1087                function: "transfer".to_string(),
1088                type_args: vec![],
1089                args: vec![],
1090            }),
1091            100_000,
1092            100,
1093            1_000_000_000,
1094            ChainId::testnet(),
1095        );
1096        // Random nonces should be different
1097        assert_ne!(txn1.nonce, txn2.nonce);
1098        // Nonce should be 32 bytes
1099        assert_eq!(txn1.nonce.len(), 32);
1100        assert_eq!(txn2.nonce.len(), 32);
1101    }
1102
1103    #[test]
1104    #[allow(deprecated)]
1105    fn test_orderless_transaction_signing_message() {
1106        let txn = create_test_orderless_transaction();
1107        let message = txn.signing_message().unwrap();
1108        assert!(!message.is_empty());
1109        // First 32 bytes should be the hash prefix
1110        assert_eq!(message.len(), 32 + txn.to_bcs().unwrap().len());
1111    }
1112
1113    #[test]
1114    #[allow(deprecated)]
1115    fn test_orderless_transaction_bcs() {
1116        let txn = create_test_orderless_transaction();
1117        let bcs = txn.to_bcs().unwrap();
1118        assert!(!bcs.is_empty());
1119    }
1120
1121    #[test]
1122    #[allow(deprecated)]
1123    fn test_signed_orderless_transaction() {
1124        use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
1125        let txn = create_test_orderless_transaction();
1126        let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
1127            public_key: Ed25519PublicKey([0u8; 32]),
1128            signature: Ed25519Signature([0u8; 64]),
1129        };
1130        let signed = SignedTransactionOrderless::new(txn, auth);
1131        assert_eq!(signed.sender(), AccountAddress::ONE);
1132        assert_eq!(signed.nonce().len(), 32);
1133    }
1134
1135    #[test]
1136    #[allow(deprecated)]
1137    fn test_signed_orderless_transaction_bcs() {
1138        use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
1139        let txn = create_test_orderless_transaction();
1140        let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
1141            public_key: Ed25519PublicKey([0u8; 32]),
1142            signature: Ed25519Signature([0u8; 64]),
1143        };
1144        let signed = SignedTransactionOrderless::new(txn, auth);
1145        let bcs = signed.to_bcs().unwrap();
1146        assert!(!bcs.is_empty());
1147    }
1148
1149    #[test]
1150    #[allow(deprecated)]
1151    fn test_signed_orderless_transaction_hash() {
1152        use crate::transaction::authenticator::{Ed25519PublicKey, Ed25519Signature};
1153        let txn = create_test_orderless_transaction();
1154        let auth = crate::transaction::TransactionAuthenticator::Ed25519 {
1155            public_key: Ed25519PublicKey([0u8; 32]),
1156            signature: Ed25519Signature([0u8; 64]),
1157        };
1158        let signed = SignedTransactionOrderless::new(txn, auth);
1159        let hash = signed.hash().unwrap();
1160        // Hash should be 32 bytes
1161        assert_eq!(hash.as_bytes().len(), 32);
1162        // Hash should be deterministic
1163        let hash2 = signed.hash().unwrap();
1164        assert_eq!(hash, hash2);
1165    }
1166
1167    #[test]
1168    fn test_multi_agent_raw_transaction() {
1169        let raw_txn = create_test_raw_transaction();
1170        let secondary = vec![AccountAddress::from_hex("0x2").unwrap()];
1171        let multi_agent = MultiAgentRawTransaction::new(raw_txn, secondary.clone());
1172        assert_eq!(multi_agent.secondary_signer_addresses, secondary);
1173    }
1174
1175    #[test]
1176    fn test_multi_agent_signing_message() {
1177        let raw_txn = create_test_raw_transaction();
1178        let secondary = vec![AccountAddress::from_hex("0x2").unwrap()];
1179        let multi_agent = MultiAgentRawTransaction::new(raw_txn, secondary);
1180        let message = multi_agent.signing_message().unwrap();
1181        assert!(!message.is_empty());
1182    }
1183}