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