1use crate::account::Account;
40use crate::error::{AptosError, AptosResult};
41use crate::transaction::authenticator::{AccountAuthenticator, TransactionAuthenticator};
42use crate::transaction::builder::{
43 DEFAULT_EXPIRATION_SECONDS, DEFAULT_GAS_UNIT_PRICE, DEFAULT_MAX_GAS_AMOUNT,
44};
45use crate::transaction::payload::TransactionPayload;
46use crate::transaction::types::{FeePayerRawTransaction, RawTransaction, SignedTransaction};
47use crate::types::{AccountAddress, ChainId};
48use std::time::{SystemTime, UNIX_EPOCH};
49
50#[derive(Debug, Clone, Default)]
78pub struct SponsoredTransactionBuilder {
79 sender_address: Option<AccountAddress>,
80 sequence_number: Option<u64>,
81 secondary_addresses: Vec<AccountAddress>,
82 fee_payer_address: Option<AccountAddress>,
83 payload: Option<TransactionPayload>,
84 max_gas_amount: u64,
85 gas_unit_price: u64,
86 expiration_timestamp_secs: Option<u64>,
87 chain_id: Option<ChainId>,
88}
89
90impl SponsoredTransactionBuilder {
91 #[must_use]
93 pub fn new() -> Self {
94 Self {
95 sender_address: None,
96 sequence_number: None,
97 secondary_addresses: Vec::new(),
98 fee_payer_address: None,
99 payload: None,
100 max_gas_amount: DEFAULT_MAX_GAS_AMOUNT,
101 gas_unit_price: DEFAULT_GAS_UNIT_PRICE,
102 expiration_timestamp_secs: None,
103 chain_id: None,
104 }
105 }
106
107 #[must_use]
109 pub fn sender(mut self, address: AccountAddress) -> Self {
110 self.sender_address = Some(address);
111 self
112 }
113
114 #[must_use]
116 pub fn sequence_number(mut self, sequence_number: u64) -> Self {
117 self.sequence_number = Some(sequence_number);
118 self
119 }
120
121 #[must_use]
126 pub fn secondary_signer(mut self, address: AccountAddress) -> Self {
127 self.secondary_addresses.push(address);
128 self
129 }
130
131 #[must_use]
133 pub fn secondary_signers(mut self, addresses: &[AccountAddress]) -> Self {
134 self.secondary_addresses.extend(addresses);
135 self
136 }
137
138 #[must_use]
140 pub fn fee_payer(mut self, address: AccountAddress) -> Self {
141 self.fee_payer_address = Some(address);
142 self
143 }
144
145 #[must_use]
147 pub fn payload(mut self, payload: TransactionPayload) -> Self {
148 self.payload = Some(payload);
149 self
150 }
151
152 #[must_use]
154 pub fn max_gas_amount(mut self, max_gas_amount: u64) -> Self {
155 self.max_gas_amount = max_gas_amount;
156 self
157 }
158
159 #[must_use]
161 pub fn gas_unit_price(mut self, gas_unit_price: u64) -> Self {
162 self.gas_unit_price = gas_unit_price;
163 self
164 }
165
166 #[must_use]
168 pub fn expiration_timestamp_secs(mut self, expiration_timestamp_secs: u64) -> Self {
169 self.expiration_timestamp_secs = Some(expiration_timestamp_secs);
170 self
171 }
172
173 #[must_use]
175 pub fn expiration_from_now(mut self, seconds: u64) -> Self {
176 let now = SystemTime::now()
177 .duration_since(UNIX_EPOCH)
178 .unwrap_or_default()
179 .as_secs();
180 self.expiration_timestamp_secs = Some(now + seconds);
181 self
182 }
183
184 #[must_use]
186 pub fn chain_id(mut self, chain_id: ChainId) -> Self {
187 self.chain_id = Some(chain_id);
188 self
189 }
190
191 pub fn build(self) -> AptosResult<FeePayerRawTransaction> {
200 let sender = self
201 .sender_address
202 .ok_or_else(|| AptosError::transaction("sender is required"))?;
203 let sequence_number = self
204 .sequence_number
205 .ok_or_else(|| AptosError::transaction("sequence_number is required"))?;
206 let payload = self
207 .payload
208 .ok_or_else(|| AptosError::transaction("payload is required"))?;
209 let chain_id = self
210 .chain_id
211 .ok_or_else(|| AptosError::transaction("chain_id is required"))?;
212 let fee_payer_address = self
213 .fee_payer_address
214 .ok_or_else(|| AptosError::transaction("fee_payer is required"))?;
215
216 let expiration_timestamp_secs = self.expiration_timestamp_secs.unwrap_or_else(|| {
217 SystemTime::now()
218 .duration_since(UNIX_EPOCH)
219 .unwrap_or_default()
220 .as_secs()
221 .saturating_add(DEFAULT_EXPIRATION_SECONDS)
222 + DEFAULT_EXPIRATION_SECONDS
223 });
224
225 let raw_txn = RawTransaction::new(
226 sender,
227 sequence_number,
228 payload,
229 self.max_gas_amount,
230 self.gas_unit_price,
231 expiration_timestamp_secs,
232 chain_id,
233 );
234
235 Ok(FeePayerRawTransaction {
236 raw_txn,
237 secondary_signer_addresses: self.secondary_addresses,
238 fee_payer_address,
239 })
240 }
241
242 pub fn build_and_sign<S, F>(
263 self,
264 sender: &S,
265 secondary_signers: &[&dyn Account],
266 fee_payer: &F,
267 ) -> AptosResult<SignedTransaction>
268 where
269 S: Account,
270 F: Account,
271 {
272 let fee_payer_txn = self.build()?;
273 sign_sponsored_transaction(&fee_payer_txn, sender, secondary_signers, fee_payer)
274 }
275}
276
277pub fn sign_sponsored_transaction<S, F>(
303 fee_payer_txn: &FeePayerRawTransaction,
304 sender: &S,
305 secondary_signers: &[&dyn Account],
306 fee_payer: &F,
307) -> AptosResult<SignedTransaction>
308where
309 S: Account,
310 F: Account,
311{
312 let signing_message = fee_payer_txn.signing_message()?;
313
314 let sender_signature = sender.sign(&signing_message)?;
316 let sender_public_key = sender.public_key_bytes();
317 let sender_auth = make_account_authenticator(
318 sender.signature_scheme(),
319 sender_public_key,
320 sender_signature,
321 )?;
322
323 let mut secondary_auths = Vec::with_capacity(secondary_signers.len());
325 for signer in secondary_signers {
326 let signature = signer.sign(&signing_message)?;
327 let public_key = signer.public_key_bytes();
328 secondary_auths.push(make_account_authenticator(
329 signer.signature_scheme(),
330 public_key,
331 signature,
332 )?);
333 }
334
335 let fee_payer_signature = fee_payer.sign(&signing_message)?;
337 let fee_payer_public_key = fee_payer.public_key_bytes();
338 let fee_payer_auth = make_account_authenticator(
339 fee_payer.signature_scheme(),
340 fee_payer_public_key,
341 fee_payer_signature,
342 )?;
343
344 let authenticator = TransactionAuthenticator::fee_payer(
345 sender_auth,
346 fee_payer_txn.secondary_signer_addresses.clone(),
347 secondary_auths,
348 fee_payer_txn.fee_payer_address,
349 fee_payer_auth,
350 );
351
352 Ok(SignedTransaction::new(
353 fee_payer_txn.raw_txn.clone(),
354 authenticator,
355 ))
356}
357
358fn make_account_authenticator(
364 scheme: u8,
365 public_key: Vec<u8>,
366 signature: Vec<u8>,
367) -> AptosResult<AccountAuthenticator> {
368 match scheme {
369 crate::crypto::ED25519_SCHEME => Ok(AccountAuthenticator::ed25519(public_key, signature)),
370 crate::crypto::MULTI_ED25519_SCHEME => Ok(AccountAuthenticator::MultiEd25519 {
371 public_key,
372 signature,
373 }),
374 crate::crypto::SINGLE_KEY_SCHEME => {
375 Ok(AccountAuthenticator::single_key(public_key, signature))
376 }
377 crate::crypto::MULTI_KEY_SCHEME => {
378 Ok(AccountAuthenticator::multi_key(public_key, signature))
379 }
380 _ => Err(AptosError::InvalidSignature(format!(
381 "unknown signature scheme: {scheme}"
382 ))),
383 }
384}
385
386#[derive(Debug, Clone)]
392pub struct PartiallySigned {
393 pub fee_payer_txn: FeePayerRawTransaction,
395 pub sender_auth: Option<AccountAuthenticator>,
397 pub secondary_auths: Vec<Option<AccountAuthenticator>>,
399 pub fee_payer_auth: Option<AccountAuthenticator>,
401}
402
403impl PartiallySigned {
404 pub fn new(fee_payer_txn: FeePayerRawTransaction) -> Self {
406 let num_secondary = fee_payer_txn.secondary_signer_addresses.len();
407 Self {
408 fee_payer_txn,
409 sender_auth: None,
410 secondary_auths: vec![None; num_secondary],
411 fee_payer_auth: None,
412 }
413 }
414
415 pub fn sign_as_sender<A: Account>(&mut self, sender: &A) -> AptosResult<()> {
422 let signing_message = self.fee_payer_txn.signing_message()?;
423 let signature = sender.sign(&signing_message)?;
424 let public_key = sender.public_key_bytes();
425 self.sender_auth = Some(make_account_authenticator(
426 sender.signature_scheme(),
427 public_key,
428 signature,
429 )?);
430 Ok(())
431 }
432
433 pub fn sign_as_secondary<A: Account>(&mut self, index: usize, signer: &A) -> AptosResult<()> {
440 if index >= self.secondary_auths.len() {
441 return Err(AptosError::transaction(format!(
442 "secondary signer index {} out of bounds (max {})",
443 index,
444 self.secondary_auths.len()
445 )));
446 }
447
448 let signing_message = self.fee_payer_txn.signing_message()?;
449 let signature = signer.sign(&signing_message)?;
450 let public_key = signer.public_key_bytes();
451 self.secondary_auths[index] = Some(make_account_authenticator(
452 signer.signature_scheme(),
453 public_key,
454 signature,
455 )?);
456 Ok(())
457 }
458
459 pub fn sign_as_fee_payer<A: Account>(&mut self, fee_payer: &A) -> AptosResult<()> {
466 let signing_message = self.fee_payer_txn.signing_message()?;
467 let signature = fee_payer.sign(&signing_message)?;
468 let public_key = fee_payer.public_key_bytes();
469 self.fee_payer_auth = Some(make_account_authenticator(
470 fee_payer.signature_scheme(),
471 public_key,
472 signature,
473 )?);
474 Ok(())
475 }
476
477 pub fn is_complete(&self) -> bool {
479 self.sender_auth.is_some()
480 && self.fee_payer_auth.is_some()
481 && self.secondary_auths.iter().all(Option::is_some)
482 }
483
484 pub fn finalize(self) -> AptosResult<SignedTransaction> {
492 let sender_auth = self
493 .sender_auth
494 .ok_or_else(|| AptosError::transaction("missing sender signature"))?;
495 let fee_payer_auth = self
496 .fee_payer_auth
497 .ok_or_else(|| AptosError::transaction("missing fee payer signature"))?;
498
499 let secondary_auths: Result<Vec<_>, _> = self
500 .secondary_auths
501 .into_iter()
502 .enumerate()
503 .map(|(i, auth)| {
504 auth.ok_or_else(|| {
505 AptosError::transaction(format!("missing secondary signer {i} signature"))
506 })
507 })
508 .collect();
509 let secondary_auths = secondary_auths?;
510
511 let authenticator = TransactionAuthenticator::fee_payer(
512 sender_auth,
513 self.fee_payer_txn.secondary_signer_addresses.clone(),
514 secondary_auths,
515 self.fee_payer_txn.fee_payer_address,
516 fee_payer_auth,
517 );
518
519 Ok(SignedTransaction::new(
520 self.fee_payer_txn.raw_txn,
521 authenticator,
522 ))
523 }
524}
525
526pub trait Sponsor: Account + Sized {
531 fn sponsor<S: Account>(
559 &self,
560 sender: &S,
561 sender_sequence_number: u64,
562 payload: TransactionPayload,
563 chain_id: ChainId,
564 ) -> AptosResult<SignedTransaction> {
565 SponsoredTransactionBuilder::new()
566 .sender(sender.address())
567 .sequence_number(sender_sequence_number)
568 .fee_payer(self.address())
569 .payload(payload)
570 .chain_id(chain_id)
571 .build_and_sign(sender, &[], self)
572 }
573
574 fn sponsor_with_gas<S: Account>(
580 &self,
581 sender: &S,
582 sender_sequence_number: u64,
583 payload: TransactionPayload,
584 chain_id: ChainId,
585 max_gas_amount: u64,
586 gas_unit_price: u64,
587 ) -> AptosResult<SignedTransaction> {
588 SponsoredTransactionBuilder::new()
589 .sender(sender.address())
590 .sequence_number(sender_sequence_number)
591 .fee_payer(self.address())
592 .payload(payload)
593 .chain_id(chain_id)
594 .max_gas_amount(max_gas_amount)
595 .gas_unit_price(gas_unit_price)
596 .build_and_sign(sender, &[], self)
597 }
598}
599
600impl<A: Account + Sized> Sponsor for A {}
602
603pub fn sponsor_transaction<S, F>(
626 sender: &S,
627 sender_sequence_number: u64,
628 fee_payer: &F,
629 payload: TransactionPayload,
630 chain_id: ChainId,
631) -> AptosResult<SignedTransaction>
632where
633 S: Account,
634 F: Account,
635{
636 SponsoredTransactionBuilder::new()
637 .sender(sender.address())
638 .sequence_number(sender_sequence_number)
639 .fee_payer(fee_payer.address())
640 .payload(payload)
641 .chain_id(chain_id)
642 .build_and_sign(sender, &[], fee_payer)
643}
644
645#[cfg(test)]
646mod tests {
647 use super::*;
648 use crate::transaction::payload::EntryFunction;
649
650 #[test]
651 fn test_builder_missing_sender() {
652 let recipient = AccountAddress::from_hex("0x123").unwrap();
653 let result = SponsoredTransactionBuilder::new()
654 .sequence_number(0)
655 .fee_payer(AccountAddress::ONE)
656 .payload(TransactionPayload::EntryFunction(
657 EntryFunction::apt_transfer(recipient, 1000).unwrap(),
658 ))
659 .chain_id(ChainId::testnet())
660 .build();
661
662 assert!(result.is_err());
663 assert!(result.unwrap_err().to_string().contains("sender"));
664 }
665
666 #[test]
667 fn test_builder_missing_fee_payer() {
668 let recipient = AccountAddress::from_hex("0x123").unwrap();
669 let result = SponsoredTransactionBuilder::new()
670 .sender(AccountAddress::ONE)
671 .sequence_number(0)
672 .payload(TransactionPayload::EntryFunction(
673 EntryFunction::apt_transfer(recipient, 1000).unwrap(),
674 ))
675 .chain_id(ChainId::testnet())
676 .build();
677
678 assert!(result.is_err());
679 assert!(result.unwrap_err().to_string().contains("fee_payer"));
680 }
681
682 #[test]
683 fn test_builder_complete() {
684 let recipient = AccountAddress::from_hex("0x123").unwrap();
685 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
686
687 let fee_payer_txn = SponsoredTransactionBuilder::new()
688 .sender(AccountAddress::ONE)
689 .sequence_number(5)
690 .fee_payer(AccountAddress::from_hex("0x3").unwrap())
691 .payload(payload.into())
692 .chain_id(ChainId::testnet())
693 .max_gas_amount(100_000)
694 .gas_unit_price(150)
695 .build()
696 .unwrap();
697
698 assert_eq!(fee_payer_txn.raw_txn.sender, AccountAddress::ONE);
699 assert_eq!(fee_payer_txn.raw_txn.sequence_number, 5);
700 assert_eq!(fee_payer_txn.raw_txn.max_gas_amount, 100_000);
701 assert_eq!(fee_payer_txn.raw_txn.gas_unit_price, 150);
702 assert_eq!(
703 fee_payer_txn.fee_payer_address,
704 AccountAddress::from_hex("0x3").unwrap()
705 );
706 }
707
708 #[test]
709 fn test_partially_signed_completion_check() {
710 let recipient = AccountAddress::from_hex("0x123").unwrap();
711 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
712
713 let fee_payer_txn = SponsoredTransactionBuilder::new()
714 .sender(AccountAddress::ONE)
715 .sequence_number(0)
716 .fee_payer(AccountAddress::from_hex("0x3").unwrap())
717 .payload(payload.into())
718 .chain_id(ChainId::testnet())
719 .build()
720 .unwrap();
721
722 let partially_signed = PartiallySigned::new(fee_payer_txn);
723 assert!(!partially_signed.is_complete());
724 }
725
726 #[test]
727 fn test_partially_signed_finalize_incomplete() {
728 let recipient = AccountAddress::from_hex("0x123").unwrap();
729 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
730
731 let fee_payer_txn = SponsoredTransactionBuilder::new()
732 .sender(AccountAddress::ONE)
733 .sequence_number(0)
734 .fee_payer(AccountAddress::from_hex("0x3").unwrap())
735 .payload(payload.into())
736 .chain_id(ChainId::testnet())
737 .build()
738 .unwrap();
739
740 let partially_signed = PartiallySigned::new(fee_payer_txn);
741 let result = partially_signed.finalize();
742
743 assert!(result.is_err());
744 assert!(result.unwrap_err().to_string().contains("missing"));
745 }
746
747 #[cfg(feature = "ed25519")]
748 #[test]
749 fn test_full_sponsored_transaction() {
750 use crate::account::Ed25519Account;
751
752 let sender = Ed25519Account::generate();
753 let fee_payer = Ed25519Account::generate();
754 let recipient = AccountAddress::from_hex("0x123").unwrap();
755
756 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
757
758 let signed_txn = SponsoredTransactionBuilder::new()
759 .sender(sender.address())
760 .sequence_number(0)
761 .fee_payer(fee_payer.address())
762 .payload(payload.into())
763 .chain_id(ChainId::testnet())
764 .build_and_sign(&sender, &[], &fee_payer)
765 .unwrap();
766
767 assert_eq!(signed_txn.raw_txn.sender, sender.address());
769 assert!(matches!(
770 signed_txn.authenticator,
771 TransactionAuthenticator::FeePayer { .. }
772 ));
773 }
774
775 #[cfg(feature = "ed25519")]
776 #[test]
777 fn test_sponsor_trait() {
778 use crate::account::Ed25519Account;
779
780 let sender = Ed25519Account::generate();
781 let sponsor = Ed25519Account::generate();
782 let recipient = AccountAddress::from_hex("0x123").unwrap();
783
784 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
785
786 let signed_txn = sponsor
788 .sponsor(&sender, 0, payload.into(), ChainId::testnet())
789 .unwrap();
790
791 assert_eq!(signed_txn.raw_txn.sender, sender.address());
792 }
793
794 #[cfg(feature = "ed25519")]
795 #[test]
796 fn test_sponsor_transaction_fn() {
797 use crate::account::Ed25519Account;
798
799 let sender = Ed25519Account::generate();
800 let fee_payer = Ed25519Account::generate();
801 let recipient = AccountAddress::from_hex("0x123").unwrap();
802
803 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
804
805 let signed_txn =
807 sponsor_transaction(&sender, 0, &fee_payer, payload.into(), ChainId::testnet())
808 .unwrap();
809
810 assert_eq!(signed_txn.raw_txn.sender, sender.address());
811 }
812
813 #[cfg(feature = "ed25519")]
814 #[test]
815 fn test_partially_signed_flow() {
816 use crate::account::Ed25519Account;
817
818 let sender = Ed25519Account::generate();
819 let fee_payer = Ed25519Account::generate();
820 let recipient = AccountAddress::from_hex("0x123").unwrap();
821
822 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
823
824 let fee_payer_txn = SponsoredTransactionBuilder::new()
826 .sender(sender.address())
827 .sequence_number(0)
828 .fee_payer(fee_payer.address())
829 .payload(payload.into())
830 .chain_id(ChainId::testnet())
831 .build()
832 .unwrap();
833
834 let mut partially_signed = PartiallySigned::new(fee_payer_txn);
836
837 assert!(!partially_signed.is_complete());
839
840 partially_signed.sign_as_sender(&sender).unwrap();
842 assert!(!partially_signed.is_complete());
843
844 partially_signed.sign_as_fee_payer(&fee_payer).unwrap();
846 assert!(partially_signed.is_complete());
847
848 let signed_txn = partially_signed.finalize().unwrap();
850 assert_eq!(signed_txn.raw_txn.sender, sender.address());
851 }
852
853 #[test]
854 fn test_builder_missing_sequence_number() {
855 let recipient = AccountAddress::from_hex("0x123").unwrap();
856 let result = SponsoredTransactionBuilder::new()
857 .sender(AccountAddress::ONE)
858 .fee_payer(AccountAddress::from_hex("0x3").unwrap())
859 .payload(TransactionPayload::EntryFunction(
860 EntryFunction::apt_transfer(recipient, 1000).unwrap(),
861 ))
862 .chain_id(ChainId::testnet())
863 .build();
864
865 assert!(result.is_err());
866 assert!(result.unwrap_err().to_string().contains("sequence_number"));
867 }
868
869 #[test]
870 fn test_builder_missing_payload() {
871 let result = SponsoredTransactionBuilder::new()
872 .sender(AccountAddress::ONE)
873 .sequence_number(0)
874 .fee_payer(AccountAddress::from_hex("0x3").unwrap())
875 .chain_id(ChainId::testnet())
876 .build();
877
878 assert!(result.is_err());
879 assert!(result.unwrap_err().to_string().contains("payload"));
880 }
881
882 #[test]
883 fn test_builder_missing_chain_id() {
884 let recipient = AccountAddress::from_hex("0x123").unwrap();
885 let result = SponsoredTransactionBuilder::new()
886 .sender(AccountAddress::ONE)
887 .sequence_number(0)
888 .fee_payer(AccountAddress::from_hex("0x3").unwrap())
889 .payload(TransactionPayload::EntryFunction(
890 EntryFunction::apt_transfer(recipient, 1000).unwrap(),
891 ))
892 .build();
893
894 assert!(result.is_err());
895 assert!(result.unwrap_err().to_string().contains("chain_id"));
896 }
897
898 #[test]
899 fn test_builder_secondary_signers() {
900 let recipient = AccountAddress::from_hex("0x123").unwrap();
901 let secondary1 = AccountAddress::from_hex("0x4").unwrap();
902 let secondary2 = AccountAddress::from_hex("0x5").unwrap();
903
904 let fee_payer_txn = SponsoredTransactionBuilder::new()
905 .sender(AccountAddress::ONE)
906 .sequence_number(0)
907 .fee_payer(AccountAddress::from_hex("0x3").unwrap())
908 .secondary_signer(secondary1)
909 .secondary_signers(&[secondary2])
910 .payload(TransactionPayload::EntryFunction(
911 EntryFunction::apt_transfer(recipient, 1000).unwrap(),
912 ))
913 .chain_id(ChainId::testnet())
914 .build()
915 .unwrap();
916
917 assert_eq!(fee_payer_txn.secondary_signer_addresses.len(), 2);
918 assert_eq!(fee_payer_txn.secondary_signer_addresses[0], secondary1);
919 assert_eq!(fee_payer_txn.secondary_signer_addresses[1], secondary2);
920 }
921
922 #[test]
923 fn test_builder_expiration_timestamp() {
924 let recipient = AccountAddress::from_hex("0x123").unwrap();
925 let expiration = 1_234_567_890_u64;
926
927 let fee_payer_txn = SponsoredTransactionBuilder::new()
928 .sender(AccountAddress::ONE)
929 .sequence_number(0)
930 .fee_payer(AccountAddress::from_hex("0x3").unwrap())
931 .payload(TransactionPayload::EntryFunction(
932 EntryFunction::apt_transfer(recipient, 1000).unwrap(),
933 ))
934 .chain_id(ChainId::testnet())
935 .expiration_timestamp_secs(expiration)
936 .build()
937 .unwrap();
938
939 assert_eq!(fee_payer_txn.raw_txn.expiration_timestamp_secs, expiration);
940 }
941
942 #[test]
943 fn test_builder_expiration_from_now() {
944 let recipient = AccountAddress::from_hex("0x123").unwrap();
945
946 let fee_payer_txn = SponsoredTransactionBuilder::new()
947 .sender(AccountAddress::ONE)
948 .sequence_number(0)
949 .fee_payer(AccountAddress::from_hex("0x3").unwrap())
950 .payload(TransactionPayload::EntryFunction(
951 EntryFunction::apt_transfer(recipient, 1000).unwrap(),
952 ))
953 .chain_id(ChainId::testnet())
954 .expiration_from_now(60)
955 .build()
956 .unwrap();
957
958 let now = SystemTime::now()
960 .duration_since(UNIX_EPOCH)
961 .unwrap()
962 .as_secs();
963 assert!(fee_payer_txn.raw_txn.expiration_timestamp_secs >= now);
964 assert!(fee_payer_txn.raw_txn.expiration_timestamp_secs <= now + 65);
965 }
966
967 #[test]
968 fn test_builder_default() {
969 let builder = SponsoredTransactionBuilder::default();
970 assert!(builder.sender_address.is_none());
971 assert!(builder.sequence_number.is_none());
972 assert!(builder.fee_payer_address.is_none());
973 assert!(builder.payload.is_none());
974 assert!(builder.chain_id.is_none());
975 }
979
980 #[test]
981 fn test_builder_new_defaults() {
982 let builder = SponsoredTransactionBuilder::new();
983 assert!(builder.sender_address.is_none());
984 assert!(builder.sequence_number.is_none());
985 assert!(builder.fee_payer_address.is_none());
986 assert!(builder.payload.is_none());
987 assert!(builder.chain_id.is_none());
988 assert_eq!(builder.max_gas_amount, DEFAULT_MAX_GAS_AMOUNT);
989 assert_eq!(builder.gas_unit_price, DEFAULT_GAS_UNIT_PRICE);
990 }
991
992 #[test]
993 fn test_builder_debug() {
994 let builder = SponsoredTransactionBuilder::new().sender(AccountAddress::ONE);
995 let debug = format!("{builder:?}");
996 assert!(debug.contains("SponsoredTransactionBuilder"));
997 }
998
999 #[cfg(feature = "ed25519")]
1000 #[test]
1001 fn test_partially_signed_with_secondary_signers() {
1002 use crate::account::Ed25519Account;
1003
1004 let sender = Ed25519Account::generate();
1005 let secondary = Ed25519Account::generate();
1006 let fee_payer = Ed25519Account::generate();
1007 let recipient = AccountAddress::from_hex("0x123").unwrap();
1008
1009 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
1010
1011 let fee_payer_txn = SponsoredTransactionBuilder::new()
1013 .sender(sender.address())
1014 .sequence_number(0)
1015 .secondary_signer(secondary.address())
1016 .fee_payer(fee_payer.address())
1017 .payload(payload.into())
1018 .chain_id(ChainId::testnet())
1019 .build()
1020 .unwrap();
1021
1022 let mut partially_signed = PartiallySigned::new(fee_payer_txn);
1023
1024 assert!(!partially_signed.is_complete());
1026
1027 partially_signed.sign_as_sender(&sender).unwrap();
1028 assert!(!partially_signed.is_complete());
1029
1030 partially_signed.sign_as_secondary(0, &secondary).unwrap();
1031 assert!(!partially_signed.is_complete());
1032
1033 partially_signed.sign_as_fee_payer(&fee_payer).unwrap();
1034 assert!(partially_signed.is_complete());
1035
1036 let signed = partially_signed.finalize().unwrap();
1037 assert_eq!(signed.raw_txn.sender, sender.address());
1038 }
1039
1040 #[cfg(feature = "ed25519")]
1041 #[test]
1042 fn test_partially_signed_secondary_index_out_of_bounds() {
1043 use crate::account::Ed25519Account;
1044
1045 let sender = Ed25519Account::generate();
1046 let fee_payer = Ed25519Account::generate();
1047 let secondary = Ed25519Account::generate();
1048 let recipient = AccountAddress::from_hex("0x123").unwrap();
1049
1050 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
1051
1052 let fee_payer_txn = SponsoredTransactionBuilder::new()
1054 .sender(sender.address())
1055 .sequence_number(0)
1056 .fee_payer(fee_payer.address())
1057 .payload(payload.into())
1058 .chain_id(ChainId::testnet())
1059 .build()
1060 .unwrap();
1061
1062 let mut partially_signed = PartiallySigned::new(fee_payer_txn);
1063
1064 let result = partially_signed.sign_as_secondary(0, &secondary);
1066 assert!(result.is_err());
1067 assert!(result.unwrap_err().to_string().contains("out of bounds"));
1068 }
1069
1070 #[cfg(feature = "ed25519")]
1071 #[test]
1072 fn test_partially_signed_finalize_missing_secondary() {
1073 use crate::account::Ed25519Account;
1074
1075 let sender = Ed25519Account::generate();
1076 let fee_payer = Ed25519Account::generate();
1077 let recipient = AccountAddress::from_hex("0x123").unwrap();
1078
1079 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
1080
1081 let fee_payer_txn = SponsoredTransactionBuilder::new()
1083 .sender(sender.address())
1084 .sequence_number(0)
1085 .secondary_signer(AccountAddress::from_hex("0x5").unwrap())
1086 .fee_payer(fee_payer.address())
1087 .payload(payload.into())
1088 .chain_id(ChainId::testnet())
1089 .build()
1090 .unwrap();
1091
1092 let mut partially_signed = PartiallySigned::new(fee_payer_txn);
1093
1094 partially_signed.sign_as_sender(&sender).unwrap();
1096 partially_signed.sign_as_fee_payer(&fee_payer).unwrap();
1097
1098 let result = partially_signed.finalize();
1100 assert!(result.is_err());
1101 assert!(result.unwrap_err().to_string().contains("secondary signer"));
1102 }
1103
1104 #[cfg(feature = "ed25519")]
1105 #[test]
1106 fn test_sponsor_with_gas() {
1107 use crate::account::Ed25519Account;
1108
1109 let sender = Ed25519Account::generate();
1110 let sponsor = Ed25519Account::generate();
1111 let recipient = AccountAddress::from_hex("0x123").unwrap();
1112
1113 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
1114
1115 let signed_txn = sponsor
1116 .sponsor_with_gas(&sender, 0, payload.into(), ChainId::testnet(), 50000, 200)
1117 .unwrap();
1118
1119 assert_eq!(signed_txn.raw_txn.sender, sender.address());
1120 assert_eq!(signed_txn.raw_txn.max_gas_amount, 50000);
1121 assert_eq!(signed_txn.raw_txn.gas_unit_price, 200);
1122 }
1123
1124 #[test]
1125 fn test_partially_signed_debug() {
1126 let recipient = AccountAddress::from_hex("0x123").unwrap();
1127 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
1128
1129 let fee_payer_txn = SponsoredTransactionBuilder::new()
1130 .sender(AccountAddress::ONE)
1131 .sequence_number(0)
1132 .fee_payer(AccountAddress::from_hex("0x3").unwrap())
1133 .payload(payload.into())
1134 .chain_id(ChainId::testnet())
1135 .build()
1136 .unwrap();
1137
1138 let partially_signed = PartiallySigned::new(fee_payer_txn);
1139 let debug = format!("{partially_signed:?}");
1140 assert!(debug.contains("PartiallySigned"));
1141 }
1142}