1use crate::account::Account;
4use crate::crypto::{AnyPublicKey, AnySignature, MultiKeyPublicKey, MultiKeySignature};
5use crate::error::{AptosError, AptosResult};
6use crate::transaction::authenticator::{AccountAuthenticator, TransactionAuthenticator};
7use crate::transaction::payload::TransactionPayload;
8use crate::transaction::types::{
9 FeePayerRawTransaction, MultiAgentRawTransaction, RawTransaction, SignedTransaction,
10};
11use crate::types::{AccountAddress, ChainId};
12use std::time::{SystemTime, UNIX_EPOCH};
13
14pub const DEFAULT_MAX_GAS_AMOUNT: u64 = 2_000_000;
16pub const DEFAULT_GAS_UNIT_PRICE: u64 = 100;
18pub const DEFAULT_EXPIRATION_SECONDS: u64 = 600; #[derive(Debug, Clone)]
43pub struct TransactionBuilder {
44 sender: Option<AccountAddress>,
45 sequence_number: Option<u64>,
46 payload: Option<TransactionPayload>,
47 max_gas_amount: u64,
48 gas_unit_price: u64,
49 expiration_timestamp_secs: Option<u64>,
50 chain_id: Option<ChainId>,
51}
52
53impl Default for TransactionBuilder {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59impl TransactionBuilder {
60 #[must_use]
62 pub fn new() -> Self {
63 Self {
64 sender: None,
65 sequence_number: None,
66 payload: None,
67 max_gas_amount: DEFAULT_MAX_GAS_AMOUNT,
68 gas_unit_price: DEFAULT_GAS_UNIT_PRICE,
69 expiration_timestamp_secs: None,
70 chain_id: None,
71 }
72 }
73
74 #[must_use]
76 pub fn sender(mut self, sender: AccountAddress) -> Self {
77 self.sender = Some(sender);
78 self
79 }
80
81 #[must_use]
83 pub fn sequence_number(mut self, sequence_number: u64) -> Self {
84 self.sequence_number = Some(sequence_number);
85 self
86 }
87
88 #[must_use]
90 pub fn payload(mut self, payload: TransactionPayload) -> Self {
91 self.payload = Some(payload);
92 self
93 }
94
95 #[must_use]
97 pub fn max_gas_amount(mut self, max_gas_amount: u64) -> Self {
98 self.max_gas_amount = max_gas_amount;
99 self
100 }
101
102 #[must_use]
104 pub fn gas_unit_price(mut self, gas_unit_price: u64) -> Self {
105 self.gas_unit_price = gas_unit_price;
106 self
107 }
108
109 #[must_use]
111 pub fn expiration_timestamp_secs(mut self, expiration_timestamp_secs: u64) -> Self {
112 self.expiration_timestamp_secs = Some(expiration_timestamp_secs);
113 self
114 }
115
116 #[must_use]
120 pub fn expiration_from_now(mut self, seconds: u64) -> Self {
121 let now = SystemTime::now()
122 .duration_since(UNIX_EPOCH)
123 .unwrap_or_default()
124 .as_secs();
125 self.expiration_timestamp_secs = Some(now.saturating_add(seconds));
126 self
127 }
128
129 #[must_use]
131 pub fn chain_id(mut self, chain_id: ChainId) -> Self {
132 self.chain_id = Some(chain_id);
133 self
134 }
135
136 pub fn build(self) -> AptosResult<RawTransaction> {
146 let sender = self
147 .sender
148 .ok_or_else(|| AptosError::transaction("sender is required"))?;
149 let sequence_number = self
150 .sequence_number
151 .ok_or_else(|| AptosError::transaction("sequence_number is required"))?;
152 let payload = self
153 .payload
154 .ok_or_else(|| AptosError::transaction("payload is required"))?;
155 let chain_id = self
156 .chain_id
157 .ok_or_else(|| AptosError::transaction("chain_id is required"))?;
158
159 let expiration_timestamp_secs = self.expiration_timestamp_secs.unwrap_or_else(|| {
160 SystemTime::now()
161 .duration_since(UNIX_EPOCH)
162 .unwrap_or_default()
163 .as_secs()
164 .saturating_add(DEFAULT_EXPIRATION_SECONDS)
165 });
166
167 Ok(RawTransaction::new(
168 sender,
169 sequence_number,
170 payload,
171 self.max_gas_amount,
172 self.gas_unit_price,
173 expiration_timestamp_secs,
174 chain_id,
175 ))
176 }
177
178 #[cfg(feature = "ed25519")]
184 pub fn build_and_sign<A: Account>(self, account: &A) -> AptosResult<SignedTransaction> {
185 let sender = self.sender.unwrap_or_else(|| account.address());
186 let raw_txn = Self {
187 sender: Some(sender),
188 ..self
189 }
190 .build()?;
191
192 sign_transaction(&raw_txn, account)
193 }
194}
195
196pub fn sign_transaction<A: Account>(
202 raw_txn: &RawTransaction,
203 account: &A,
204) -> AptosResult<SignedTransaction> {
205 let signing_message = raw_txn.signing_message()?;
206 let signature = account.sign(&signing_message)?;
207 let public_key = account.public_key_bytes();
208
209 let authenticator =
210 make_transaction_authenticator(account.signature_scheme(), public_key, signature)?;
211
212 Ok(SignedTransaction::new(raw_txn.clone(), authenticator))
213}
214
215fn make_transaction_authenticator(
221 scheme: u8,
222 public_key: Vec<u8>,
223 signature: Vec<u8>,
224) -> AptosResult<TransactionAuthenticator> {
225 match scheme {
226 crate::crypto::ED25519_SCHEME => {
227 Ok(TransactionAuthenticator::ed25519(public_key, signature))
228 }
229 crate::crypto::MULTI_ED25519_SCHEME => Ok(TransactionAuthenticator::multi_ed25519(
230 public_key, signature,
231 )),
232 crate::crypto::MULTI_KEY_SCHEME => {
233 let _ = MultiKeyPublicKey::from_bytes(&public_key)?;
234 let _ = MultiKeySignature::from_bytes(&signature)?;
235 Ok(TransactionAuthenticator::single_sender(
236 AccountAuthenticator::multi_key(public_key, signature),
237 ))
238 }
239 crate::crypto::SINGLE_KEY_SCHEME => {
240 let _ = AnyPublicKey::from_bcs_bytes(&public_key)?;
241 let _ = AnySignature::from_bcs_bytes(&signature)?;
242 Ok(TransactionAuthenticator::single_sender(
243 AccountAuthenticator::single_key(public_key, signature),
244 ))
245 }
246 _ => Err(AptosError::InvalidSignature(format!(
247 "unknown signature scheme: {scheme}"
248 ))),
249 }
250}
251
252fn make_account_authenticator(
258 scheme: u8,
259 public_key: Vec<u8>,
260 signature: Vec<u8>,
261) -> AptosResult<AccountAuthenticator> {
262 match scheme {
263 crate::crypto::ED25519_SCHEME => Ok(AccountAuthenticator::ed25519(public_key, signature)),
264 crate::crypto::MULTI_ED25519_SCHEME => Ok(AccountAuthenticator::MultiEd25519 {
265 public_key,
266 signature,
267 }),
268 crate::crypto::SINGLE_KEY_SCHEME => {
269 let _ = AnyPublicKey::from_bcs_bytes(&public_key)?;
270 let _ = AnySignature::from_bcs_bytes(&signature)?;
271 Ok(AccountAuthenticator::single_key(public_key, signature))
272 }
273 crate::crypto::MULTI_KEY_SCHEME => {
274 let _ = MultiKeyPublicKey::from_bytes(&public_key)?;
275 let _ = MultiKeySignature::from_bytes(&signature)?;
276 Ok(AccountAuthenticator::multi_key(public_key, signature))
277 }
278 _ => Err(AptosError::InvalidSignature(format!(
279 "unknown signature scheme: {scheme}"
280 ))),
281 }
282}
283
284pub fn sign_multi_agent_transaction<A: Account>(
290 multi_agent: &MultiAgentRawTransaction,
291 sender: &A,
292 secondary_signers: &[&dyn Account],
293) -> AptosResult<SignedTransaction> {
294 let signing_message = multi_agent.signing_message()?;
295
296 let sender_signature = sender.sign(&signing_message)?;
298 let sender_public_key = sender.public_key_bytes();
299 let sender_auth = make_account_authenticator(
300 sender.signature_scheme(),
301 sender_public_key,
302 sender_signature,
303 )?;
304
305 let mut secondary_auths = Vec::with_capacity(secondary_signers.len());
307 for signer in secondary_signers {
308 let signature = signer.sign(&signing_message)?;
309 let public_key = signer.public_key_bytes();
310 secondary_auths.push(make_account_authenticator(
311 signer.signature_scheme(),
312 public_key,
313 signature,
314 )?);
315 }
316
317 let authenticator = TransactionAuthenticator::multi_agent(
318 sender_auth,
319 multi_agent.secondary_signer_addresses.clone(),
320 secondary_auths,
321 );
322
323 Ok(SignedTransaction::new(
324 multi_agent.raw_txn.clone(),
325 authenticator,
326 ))
327}
328
329pub fn sign_fee_payer_transaction<A: Account>(
335 fee_payer_txn: &FeePayerRawTransaction,
336 sender: &A,
337 secondary_signers: &[&dyn Account],
338 fee_payer: &dyn Account,
339) -> AptosResult<SignedTransaction> {
340 let signing_message = fee_payer_txn.signing_message()?;
341
342 let sender_signature = sender.sign(&signing_message)?;
344 let sender_public_key = sender.public_key_bytes();
345 let sender_auth = make_account_authenticator(
346 sender.signature_scheme(),
347 sender_public_key,
348 sender_signature,
349 )?;
350
351 let mut secondary_auths = Vec::with_capacity(secondary_signers.len());
353 for signer in secondary_signers {
354 let signature = signer.sign(&signing_message)?;
355 let public_key = signer.public_key_bytes();
356 secondary_auths.push(make_account_authenticator(
357 signer.signature_scheme(),
358 public_key,
359 signature,
360 )?);
361 }
362
363 let fee_payer_signature = fee_payer.sign(&signing_message)?;
365 let fee_payer_public_key = fee_payer.public_key_bytes();
366 let fee_payer_auth = make_account_authenticator(
367 fee_payer.signature_scheme(),
368 fee_payer_public_key,
369 fee_payer_signature,
370 )?;
371
372 let authenticator = TransactionAuthenticator::fee_payer(
373 sender_auth,
374 fee_payer_txn.secondary_signer_addresses.clone(),
375 secondary_auths,
376 fee_payer_txn.fee_payer_address,
377 fee_payer_auth,
378 );
379
380 Ok(SignedTransaction::new(
381 fee_payer_txn.raw_txn.clone(),
382 authenticator,
383 ))
384}
385
386pub fn build_simulation_signed_multi_agent(
400 multi_agent: &MultiAgentRawTransaction,
401) -> SignedTransaction {
402 let sender_auth = AccountAuthenticator::no_account_authenticator();
403 let secondary_auths: Vec<AccountAuthenticator> = multi_agent
404 .secondary_signer_addresses
405 .iter()
406 .map(|_| AccountAuthenticator::no_account_authenticator())
407 .collect();
408 let authenticator = TransactionAuthenticator::multi_agent(
409 sender_auth,
410 multi_agent.secondary_signer_addresses.clone(),
411 secondary_auths,
412 );
413 SignedTransaction::new(multi_agent.raw_txn.clone(), authenticator)
414}
415
416pub fn build_simulation_signed_fee_payer(
431 fee_payer_txn: &FeePayerRawTransaction,
432) -> SignedTransaction {
433 let sender_auth = AccountAuthenticator::no_account_authenticator();
434 let secondary_auths: Vec<AccountAuthenticator> = fee_payer_txn
435 .secondary_signer_addresses
436 .iter()
437 .map(|_| AccountAuthenticator::no_account_authenticator())
438 .collect();
439 let fee_payer_auth = AccountAuthenticator::no_account_authenticator();
440 let authenticator = TransactionAuthenticator::fee_payer(
441 sender_auth,
442 fee_payer_txn.secondary_signer_addresses.clone(),
443 secondary_auths,
444 fee_payer_txn.fee_payer_address,
445 fee_payer_auth,
446 );
447 SignedTransaction::new(fee_payer_txn.raw_txn.clone(), authenticator)
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453 use crate::transaction::payload::EntryFunction;
454
455 #[test]
456 fn test_builder_missing_fields() {
457 let result = TransactionBuilder::new().build();
458 assert!(result.is_err());
459 }
460
461 #[test]
462 fn test_builder_complete() {
463 let recipient = AccountAddress::from_hex("0x123").unwrap();
464 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
465
466 let txn = TransactionBuilder::new()
467 .sender(AccountAddress::ONE)
468 .sequence_number(0)
469 .payload(payload.into())
470 .chain_id(ChainId::testnet())
471 .build()
472 .unwrap();
473
474 assert_eq!(txn.sender, AccountAddress::ONE);
475 assert_eq!(txn.sequence_number, 0);
476 assert_eq!(txn.max_gas_amount, DEFAULT_MAX_GAS_AMOUNT);
477 assert_eq!(txn.gas_unit_price, DEFAULT_GAS_UNIT_PRICE);
478 }
479
480 #[test]
481 fn test_builder_custom_gas() {
482 let recipient = AccountAddress::from_hex("0x123").unwrap();
483 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
484
485 let txn = TransactionBuilder::new()
486 .sender(AccountAddress::ONE)
487 .sequence_number(0)
488 .payload(payload.into())
489 .max_gas_amount(500_000)
490 .gas_unit_price(200)
491 .chain_id(ChainId::testnet())
492 .build()
493 .unwrap();
494
495 assert_eq!(txn.max_gas_amount, 500_000);
496 assert_eq!(txn.gas_unit_price, 200);
497 }
498
499 #[test]
500 fn test_builder_missing_sender() {
501 let recipient = AccountAddress::from_hex("0x123").unwrap();
502 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
503
504 let result = TransactionBuilder::new()
505 .sequence_number(0)
506 .payload(payload.into())
507 .chain_id(ChainId::testnet())
508 .build();
509
510 assert!(result.is_err());
511 }
512
513 #[test]
514 fn test_builder_missing_payload() {
515 let result = TransactionBuilder::new()
516 .sender(AccountAddress::ONE)
517 .sequence_number(0)
518 .chain_id(ChainId::testnet())
519 .build();
520
521 assert!(result.is_err());
522 }
523
524 #[test]
525 fn test_builder_missing_chain_id() {
526 let recipient = AccountAddress::from_hex("0x123").unwrap();
527 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
528
529 let result = TransactionBuilder::new()
530 .sender(AccountAddress::ONE)
531 .sequence_number(0)
532 .payload(payload.into())
533 .build();
534
535 assert!(result.is_err());
536 }
537
538 #[test]
539 fn test_builder_custom_expiration() {
540 let recipient = AccountAddress::from_hex("0x123").unwrap();
541 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
542 let custom_expiration = 9_999_999_999;
543
544 let txn = TransactionBuilder::new()
545 .sender(AccountAddress::ONE)
546 .sequence_number(0)
547 .payload(payload.into())
548 .expiration_timestamp_secs(custom_expiration)
549 .chain_id(ChainId::testnet())
550 .build()
551 .unwrap();
552
553 assert_eq!(txn.expiration_timestamp_secs, custom_expiration);
554 }
555
556 #[test]
557 fn test_default_expiration() {
558 let now = std::time::SystemTime::now()
560 .duration_since(std::time::UNIX_EPOCH)
561 .unwrap()
562 .as_secs();
563
564 let recipient = AccountAddress::from_hex("0x123").unwrap();
565 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
566
567 let txn = TransactionBuilder::new()
568 .sender(AccountAddress::ONE)
569 .sequence_number(0)
570 .payload(payload.into())
571 .chain_id(ChainId::testnet())
572 .build()
573 .unwrap();
574
575 let expected_min = now + DEFAULT_EXPIRATION_SECONDS - 5;
577 let expected_max = now + DEFAULT_EXPIRATION_SECONDS + 5;
578 assert!(txn.expiration_timestamp_secs >= expected_min);
579 assert!(txn.expiration_timestamp_secs <= expected_max);
580 }
581
582 #[cfg(feature = "ed25519")]
583 #[test]
584 fn test_sign_transaction() {
585 use crate::account::Ed25519Account;
586
587 let account = Ed25519Account::generate();
588 let recipient = AccountAddress::from_hex("0x123").unwrap();
589 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
590
591 let txn = TransactionBuilder::new()
592 .sender(account.address())
593 .sequence_number(0)
594 .payload(payload.into())
595 .chain_id(ChainId::testnet())
596 .build()
597 .unwrap();
598
599 let signed = sign_transaction(&txn, &account).unwrap();
600 assert_eq!(signed.sender(), account.address());
601 signed.verify_signature().unwrap();
602 }
603
604 #[cfg(feature = "ed25519")]
605 #[test]
606 fn test_sign_transaction_with_ed25519_single_key_account() {
607 use crate::account::Ed25519SingleKeyAccount;
608 use crate::transaction::authenticator::{AccountAuthenticator, TransactionAuthenticator};
609
610 let account = Ed25519SingleKeyAccount::generate();
611 let recipient = AccountAddress::from_hex("0x123").unwrap();
612 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
613
614 let txn = TransactionBuilder::new()
615 .sender(account.address())
616 .sequence_number(0)
617 .payload(payload.into())
618 .chain_id(ChainId::testnet())
619 .build()
620 .unwrap();
621
622 let signed = sign_transaction(&txn, &account).unwrap();
623 match &signed.authenticator {
624 TransactionAuthenticator::SingleSender { sender } => {
625 assert!(matches!(sender, AccountAuthenticator::SingleKey { .. }));
626 }
627 _ => panic!("expected SingleSender authenticator"),
628 }
629 signed.verify_signature().unwrap();
630 }
631
632 #[cfg(all(feature = "ed25519", feature = "secp256k1"))]
633 #[test]
634 fn test_sign_transaction_with_mixed_multi_key_account() {
635 use crate::account::{AnyPrivateKey, MultiKeyAccount};
636 use crate::crypto::{Ed25519PrivateKey, Secp256k1PrivateKey};
637
638 let account = MultiKeyAccount::new(
639 vec![
640 AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()),
641 AnyPrivateKey::secp256k1(Secp256k1PrivateKey::generate()),
642 AnyPrivateKey::ed25519(Ed25519PrivateKey::generate()),
643 ],
644 2,
645 )
646 .unwrap();
647 let recipient = AccountAddress::from_hex("0x123").unwrap();
648 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
649
650 let txn = TransactionBuilder::new()
651 .sender(account.address())
652 .sequence_number(0)
653 .payload(payload.into())
654 .chain_id(ChainId::testnet())
655 .build()
656 .unwrap();
657
658 let signed = sign_transaction(&txn, &account).unwrap();
659 assert_eq!(signed.sender(), account.address());
660 signed.verify_signature().unwrap();
661 }
662
663 #[cfg(feature = "ed25519")]
664 #[test]
665 fn test_sign_multi_agent_transaction() {
666 use crate::account::{Account, Ed25519Account};
667
668 let sender = Ed25519Account::generate();
669 let secondary = Ed25519Account::generate();
670 let recipient = AccountAddress::from_hex("0x123").unwrap();
671 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
672
673 let raw_txn = TransactionBuilder::new()
674 .sender(sender.address())
675 .sequence_number(0)
676 .payload(payload.into())
677 .chain_id(ChainId::testnet())
678 .build()
679 .unwrap();
680
681 let multi_agent = MultiAgentRawTransaction {
682 raw_txn,
683 secondary_signer_addresses: vec![secondary.address()],
684 };
685
686 let secondary_signers: Vec<&dyn Account> = vec![&secondary];
687 let signed =
688 sign_multi_agent_transaction(&multi_agent, &sender, &secondary_signers).unwrap();
689 assert_eq!(signed.sender(), sender.address());
690 signed.verify_signature().unwrap();
691 }
692
693 #[cfg(feature = "ed25519")]
694 #[test]
695 fn test_sign_multi_agent_transaction_with_single_key_accounts() {
696 use crate::account::{Account, Ed25519SingleKeyAccount};
697 use crate::transaction::authenticator::{AccountAuthenticator, TransactionAuthenticator};
698
699 let sender = Ed25519SingleKeyAccount::generate();
700 let secondary = Ed25519SingleKeyAccount::generate();
701 let recipient = AccountAddress::from_hex("0x123").unwrap();
702 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
703
704 let raw_txn = TransactionBuilder::new()
705 .sender(sender.address())
706 .sequence_number(0)
707 .payload(payload.into())
708 .chain_id(ChainId::testnet())
709 .build()
710 .unwrap();
711
712 let multi_agent = MultiAgentRawTransaction {
713 raw_txn,
714 secondary_signer_addresses: vec![secondary.address()],
715 };
716
717 let secondary_signers: Vec<&dyn Account> = vec![&secondary];
718 let signed =
719 sign_multi_agent_transaction(&multi_agent, &sender, &secondary_signers).unwrap();
720 match &signed.authenticator {
721 TransactionAuthenticator::MultiAgent {
722 sender,
723 secondary_signers,
724 ..
725 } => {
726 assert!(matches!(sender, AccountAuthenticator::SingleKey { .. }));
727 assert!(matches!(
728 secondary_signers[0],
729 AccountAuthenticator::SingleKey { .. }
730 ));
731 }
732 _ => panic!("expected MultiAgent authenticator"),
733 }
734 signed.verify_signature().unwrap();
735 }
736
737 #[cfg(feature = "ed25519")]
738 #[test]
739 fn test_multi_agent_verify_signature_rejects_secondary_signer_length_mismatch() {
740 use crate::account::{Account, Ed25519Account};
741 use crate::transaction::authenticator::{AccountAuthenticator, TransactionAuthenticator};
742
743 let sender = Ed25519Account::generate();
744 let secondary = Ed25519Account::generate();
745 let recipient = AccountAddress::from_hex("0x123").unwrap();
746 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
747
748 let raw_txn = TransactionBuilder::new()
749 .sender(sender.address())
750 .sequence_number(0)
751 .payload(payload.into())
752 .chain_id(ChainId::testnet())
753 .build()
754 .unwrap();
755
756 let multi_agent = MultiAgentRawTransaction {
757 raw_txn,
758 secondary_signer_addresses: vec![secondary.address()],
759 };
760
761 let secondary_signers: Vec<&dyn Account> = vec![&secondary];
762 let signed =
763 sign_multi_agent_transaction(&multi_agent, &sender, &secondary_signers).unwrap();
764 signed.verify_signature().unwrap();
765
766 let mut missing_secondary_signer = signed.clone();
767 if let TransactionAuthenticator::MultiAgent {
768 secondary_signers, ..
769 } = &mut missing_secondary_signer.authenticator
770 {
771 secondary_signers.clear();
772 } else {
773 panic!("expected MultiAgent authenticator");
774 }
775 assert!(missing_secondary_signer.verify_signature().is_err());
776
777 let mut extra_secondary_signer = signed;
778 if let TransactionAuthenticator::MultiAgent {
779 secondary_signers, ..
780 } = &mut extra_secondary_signer.authenticator
781 {
782 secondary_signers.push(AccountAuthenticator::NoAccountAuthenticator);
783 } else {
784 panic!("expected MultiAgent authenticator");
785 }
786 assert!(extra_secondary_signer.verify_signature().is_err());
787 }
788
789 #[cfg(feature = "ed25519")]
790 #[test]
791 fn test_sign_fee_payer_transaction() {
792 use crate::account::Ed25519Account;
793
794 let sender = Ed25519Account::generate();
795 let fee_payer = Ed25519Account::generate();
796 let recipient = AccountAddress::from_hex("0x123").unwrap();
797 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
798
799 let raw_txn = TransactionBuilder::new()
800 .sender(sender.address())
801 .sequence_number(0)
802 .payload(payload.into())
803 .chain_id(ChainId::testnet())
804 .build()
805 .unwrap();
806
807 let fee_payer_txn = FeePayerRawTransaction {
808 raw_txn,
809 secondary_signer_addresses: vec![],
810 fee_payer_address: fee_payer.address(),
811 };
812
813 let signed = sign_fee_payer_transaction(&fee_payer_txn, &sender, &[], &fee_payer).unwrap();
814 assert_eq!(signed.sender(), sender.address());
815 signed.verify_signature().unwrap();
816 }
817
818 #[cfg(feature = "ed25519")]
819 #[test]
820 fn test_sign_fee_payer_transaction_with_multi_key_accounts() {
821 use crate::account::{AnyPrivateKey, MultiKeyAccount};
822 use crate::crypto::Ed25519PrivateKey;
823 use crate::transaction::authenticator::{AccountAuthenticator, TransactionAuthenticator};
824
825 let sender = MultiKeyAccount::new(
826 vec![AnyPrivateKey::ed25519(Ed25519PrivateKey::generate())],
827 1,
828 )
829 .unwrap();
830 let fee_payer = MultiKeyAccount::new(
831 vec![AnyPrivateKey::ed25519(Ed25519PrivateKey::generate())],
832 1,
833 )
834 .unwrap();
835 let recipient = AccountAddress::from_hex("0x123").unwrap();
836 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
837
838 let raw_txn = TransactionBuilder::new()
839 .sender(sender.address())
840 .sequence_number(0)
841 .payload(payload.into())
842 .chain_id(ChainId::testnet())
843 .build()
844 .unwrap();
845
846 let fee_payer_txn = FeePayerRawTransaction::new_simple(raw_txn, fee_payer.address());
847 let signed = sign_fee_payer_transaction(&fee_payer_txn, &sender, &[], &fee_payer).unwrap();
848 match &signed.authenticator {
849 TransactionAuthenticator::FeePayer {
850 sender,
851 fee_payer_signer,
852 ..
853 } => {
854 assert!(matches!(sender, AccountAuthenticator::MultiKey { .. }));
855 assert!(matches!(
856 fee_payer_signer,
857 AccountAuthenticator::MultiKey { .. }
858 ));
859 }
860 _ => panic!("expected FeePayer authenticator"),
861 }
862 signed.verify_signature().unwrap();
863 }
864
865 #[cfg(feature = "ed25519")]
866 #[test]
867 fn test_fee_payer_verify_signature_rejects_secondary_signer_length_mismatch() {
868 use crate::account::{Account, Ed25519Account};
869 use crate::transaction::authenticator::{AccountAuthenticator, TransactionAuthenticator};
870
871 let sender = Ed25519Account::generate();
872 let secondary = Ed25519Account::generate();
873 let fee_payer = Ed25519Account::generate();
874 let recipient = AccountAddress::from_hex("0x123").unwrap();
875 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
876
877 let raw_txn = TransactionBuilder::new()
878 .sender(sender.address())
879 .sequence_number(0)
880 .payload(payload.into())
881 .chain_id(ChainId::testnet())
882 .build()
883 .unwrap();
884
885 let fee_payer_txn = FeePayerRawTransaction {
886 raw_txn,
887 secondary_signer_addresses: vec![secondary.address()],
888 fee_payer_address: fee_payer.address(),
889 };
890
891 let secondary_signers: Vec<&dyn Account> = vec![&secondary];
892 let signed =
893 sign_fee_payer_transaction(&fee_payer_txn, &sender, &secondary_signers, &fee_payer)
894 .unwrap();
895 signed.verify_signature().unwrap();
896
897 let mut missing_secondary_signer = signed.clone();
898 if let TransactionAuthenticator::FeePayer {
899 secondary_signers, ..
900 } = &mut missing_secondary_signer.authenticator
901 {
902 secondary_signers.clear();
903 } else {
904 panic!("expected FeePayer authenticator");
905 }
906 assert!(missing_secondary_signer.verify_signature().is_err());
907
908 let mut extra_secondary_signer = signed;
909 if let TransactionAuthenticator::FeePayer {
910 secondary_signers, ..
911 } = &mut extra_secondary_signer.authenticator
912 {
913 secondary_signers.push(AccountAuthenticator::NoAccountAuthenticator);
914 } else {
915 panic!("expected FeePayer authenticator");
916 }
917 assert!(extra_secondary_signer.verify_signature().is_err());
918 }
919
920 #[test]
921 fn test_build_simulation_signed_multi_agent() {
922 use crate::transaction::authenticator::{AccountAuthenticator, TransactionAuthenticator};
923
924 let recipient = AccountAddress::from_hex("0x123").unwrap();
925 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
926 let raw_txn = TransactionBuilder::new()
927 .sender(AccountAddress::ONE)
928 .sequence_number(0)
929 .payload(payload.into())
930 .chain_id(ChainId::testnet())
931 .build()
932 .unwrap();
933
934 let secondary_addr = AccountAddress::from_hex("0x456").unwrap();
935 let multi_agent = MultiAgentRawTransaction::new(raw_txn.clone(), vec![secondary_addr]);
936
937 let signed = build_simulation_signed_multi_agent(&multi_agent);
938 assert_eq!(signed.sender(), AccountAddress::ONE);
939 assert!(signed.to_bcs().is_ok());
940
941 match &signed.authenticator {
942 TransactionAuthenticator::MultiAgent {
943 sender,
944 secondary_signer_addresses,
945 secondary_signers,
946 } => {
947 assert!(matches!(
948 sender,
949 AccountAuthenticator::NoAccountAuthenticator
950 ));
951 assert_eq!(secondary_signer_addresses.len(), 1);
952 assert_eq!(secondary_signer_addresses[0], secondary_addr);
953 assert_eq!(secondary_signers.len(), 1);
954 assert!(matches!(
955 &secondary_signers[0],
956 AccountAuthenticator::NoAccountAuthenticator
957 ));
958 }
959 _ => panic!("expected MultiAgent authenticator"),
960 }
961 }
962
963 #[test]
964 fn test_build_simulation_signed_fee_payer() {
965 use crate::transaction::authenticator::{AccountAuthenticator, TransactionAuthenticator};
966
967 let recipient = AccountAddress::from_hex("0x123").unwrap();
968 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
969 let raw_txn = TransactionBuilder::new()
970 .sender(AccountAddress::ONE)
971 .sequence_number(0)
972 .payload(payload.into())
973 .chain_id(ChainId::testnet())
974 .build()
975 .unwrap();
976
977 let fee_payer_addr = AccountAddress::from_hex("0x789").unwrap();
978 let fee_payer_txn = FeePayerRawTransaction::new_simple(raw_txn.clone(), fee_payer_addr);
979
980 let signed = build_simulation_signed_fee_payer(&fee_payer_txn);
981 assert_eq!(signed.sender(), AccountAddress::ONE);
982 assert!(signed.to_bcs().is_ok());
983
984 match &signed.authenticator {
985 TransactionAuthenticator::FeePayer {
986 sender,
987 fee_payer_address,
988 fee_payer_signer,
989 ..
990 } => {
991 assert!(matches!(
992 sender,
993 AccountAuthenticator::NoAccountAuthenticator
994 ));
995 assert_eq!(*fee_payer_address, fee_payer_addr);
996 assert!(matches!(
997 fee_payer_signer,
998 AccountAuthenticator::NoAccountAuthenticator
999 ));
1000 }
1001 _ => panic!("expected FeePayer authenticator"),
1002 }
1003 }
1004
1005 #[test]
1006 fn test_default_impl() {
1007 let builder = TransactionBuilder::default();
1008 assert!(builder.sender.is_none());
1010 assert!(builder.sequence_number.is_none());
1011 assert!(builder.payload.is_none());
1012 assert_eq!(builder.max_gas_amount, DEFAULT_MAX_GAS_AMOUNT);
1013 assert_eq!(builder.gas_unit_price, DEFAULT_GAS_UNIT_PRICE);
1014 }
1015}