1use crate::account::Account;
4use crate::error::{AptosError, AptosResult};
5use crate::transaction::authenticator::{AccountAuthenticator, TransactionAuthenticator};
6use crate::transaction::payload::TransactionPayload;
7use crate::transaction::types::{
8 FeePayerRawTransaction, MultiAgentRawTransaction, RawTransaction, SignedTransaction,
9};
10use crate::types::{AccountAddress, ChainId};
11use std::time::{SystemTime, UNIX_EPOCH};
12
13pub const DEFAULT_MAX_GAS_AMOUNT: u64 = 200_000;
15pub const DEFAULT_GAS_UNIT_PRICE: u64 = 100;
17pub const DEFAULT_EXPIRATION_SECONDS: u64 = 600; #[derive(Debug, Clone)]
42pub struct TransactionBuilder {
43 sender: Option<AccountAddress>,
44 sequence_number: Option<u64>,
45 payload: Option<TransactionPayload>,
46 max_gas_amount: u64,
47 gas_unit_price: u64,
48 expiration_timestamp_secs: Option<u64>,
49 chain_id: Option<ChainId>,
50}
51
52impl Default for TransactionBuilder {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl TransactionBuilder {
59 #[must_use]
61 pub fn new() -> Self {
62 Self {
63 sender: None,
64 sequence_number: None,
65 payload: None,
66 max_gas_amount: DEFAULT_MAX_GAS_AMOUNT,
67 gas_unit_price: DEFAULT_GAS_UNIT_PRICE,
68 expiration_timestamp_secs: None,
69 chain_id: None,
70 }
71 }
72
73 #[must_use]
75 pub fn sender(mut self, sender: AccountAddress) -> Self {
76 self.sender = Some(sender);
77 self
78 }
79
80 #[must_use]
82 pub fn sequence_number(mut self, sequence_number: u64) -> Self {
83 self.sequence_number = Some(sequence_number);
84 self
85 }
86
87 #[must_use]
89 pub fn payload(mut self, payload: TransactionPayload) -> Self {
90 self.payload = Some(payload);
91 self
92 }
93
94 #[must_use]
96 pub fn max_gas_amount(mut self, max_gas_amount: u64) -> Self {
97 self.max_gas_amount = max_gas_amount;
98 self
99 }
100
101 #[must_use]
103 pub fn gas_unit_price(mut self, gas_unit_price: u64) -> Self {
104 self.gas_unit_price = gas_unit_price;
105 self
106 }
107
108 #[must_use]
110 pub fn expiration_timestamp_secs(mut self, expiration_timestamp_secs: u64) -> Self {
111 self.expiration_timestamp_secs = Some(expiration_timestamp_secs);
112 self
113 }
114
115 #[must_use]
119 pub fn expiration_from_now(mut self, seconds: u64) -> Self {
120 let now = SystemTime::now()
121 .duration_since(UNIX_EPOCH)
122 .unwrap_or_default()
123 .as_secs();
124 self.expiration_timestamp_secs = Some(now.saturating_add(seconds));
125 self
126 }
127
128 #[must_use]
130 pub fn chain_id(mut self, chain_id: ChainId) -> Self {
131 self.chain_id = Some(chain_id);
132 self
133 }
134
135 pub fn build(self) -> AptosResult<RawTransaction> {
145 let sender = self
146 .sender
147 .ok_or_else(|| AptosError::transaction("sender is required"))?;
148 let sequence_number = self
149 .sequence_number
150 .ok_or_else(|| AptosError::transaction("sequence_number is required"))?;
151 let payload = self
152 .payload
153 .ok_or_else(|| AptosError::transaction("payload is required"))?;
154 let chain_id = self
155 .chain_id
156 .ok_or_else(|| AptosError::transaction("chain_id is required"))?;
157
158 let expiration_timestamp_secs = self.expiration_timestamp_secs.unwrap_or_else(|| {
159 SystemTime::now()
160 .duration_since(UNIX_EPOCH)
161 .unwrap_or_default()
162 .as_secs()
163 .saturating_add(DEFAULT_EXPIRATION_SECONDS)
164 });
165
166 Ok(RawTransaction::new(
167 sender,
168 sequence_number,
169 payload,
170 self.max_gas_amount,
171 self.gas_unit_price,
172 expiration_timestamp_secs,
173 chain_id,
174 ))
175 }
176
177 #[cfg(feature = "ed25519")]
183 pub fn build_and_sign<A: Account>(self, account: &A) -> AptosResult<SignedTransaction> {
184 let sender = self.sender.unwrap_or_else(|| account.address());
185 let raw_txn = Self {
186 sender: Some(sender),
187 ..self
188 }
189 .build()?;
190
191 sign_transaction(&raw_txn, account)
192 }
193}
194
195pub fn sign_transaction<A: Account>(
201 raw_txn: &RawTransaction,
202 account: &A,
203) -> AptosResult<SignedTransaction> {
204 let signing_message = raw_txn.signing_message()?;
205 let signature = account.sign(&signing_message)?;
206 let public_key = account.public_key_bytes();
207
208 let authenticator =
209 make_transaction_authenticator(account.signature_scheme(), public_key, signature)?;
210
211 Ok(SignedTransaction::new(raw_txn.clone(), authenticator))
212}
213
214fn make_transaction_authenticator(
220 scheme: u8,
221 public_key: Vec<u8>,
222 signature: Vec<u8>,
223) -> AptosResult<TransactionAuthenticator> {
224 match scheme {
225 crate::crypto::ED25519_SCHEME => {
226 Ok(TransactionAuthenticator::ed25519(public_key, signature))
227 }
228 crate::crypto::MULTI_ED25519_SCHEME => Ok(TransactionAuthenticator::multi_ed25519(
229 public_key, signature,
230 )),
231 crate::crypto::MULTI_KEY_SCHEME => {
232 Ok(TransactionAuthenticator::single_sender(
234 AccountAuthenticator::multi_key(public_key, signature),
235 ))
236 }
237 crate::crypto::SINGLE_KEY_SCHEME => {
238 Ok(TransactionAuthenticator::single_sender(
241 AccountAuthenticator::single_key(public_key, signature),
242 ))
243 }
244 #[cfg(feature = "keyless")]
245 crate::crypto::KEYLESS_SCHEME => {
246 Ok(TransactionAuthenticator::single_sender(
250 AccountAuthenticator::keyless(public_key, signature),
251 ))
252 }
253 _ => Err(AptosError::InvalidSignature(format!(
254 "unknown signature scheme: {scheme}"
255 ))),
256 }
257}
258
259fn make_account_authenticator(
265 scheme: u8,
266 public_key: Vec<u8>,
267 signature: Vec<u8>,
268) -> AptosResult<AccountAuthenticator> {
269 match scheme {
270 crate::crypto::ED25519_SCHEME => Ok(AccountAuthenticator::ed25519(public_key, signature)),
271 crate::crypto::MULTI_ED25519_SCHEME => Ok(AccountAuthenticator::MultiEd25519 {
272 public_key,
273 signature,
274 }),
275 crate::crypto::SINGLE_KEY_SCHEME => {
276 Ok(AccountAuthenticator::single_key(public_key, signature))
277 }
278 crate::crypto::MULTI_KEY_SCHEME => {
279 Ok(AccountAuthenticator::multi_key(public_key, signature))
280 }
281 #[cfg(feature = "keyless")]
282 crate::crypto::KEYLESS_SCHEME => Ok(AccountAuthenticator::keyless(public_key, signature)),
283 _ => Err(AptosError::InvalidSignature(format!(
284 "unknown signature scheme: {scheme}"
285 ))),
286 }
287}
288
289pub fn sign_multi_agent_transaction<A: Account>(
295 multi_agent: &MultiAgentRawTransaction,
296 sender: &A,
297 secondary_signers: &[&dyn Account],
298) -> AptosResult<SignedTransaction> {
299 let signing_message = multi_agent.signing_message()?;
300
301 let sender_signature = sender.sign(&signing_message)?;
303 let sender_public_key = sender.public_key_bytes();
304 let sender_auth = make_account_authenticator(
305 sender.signature_scheme(),
306 sender_public_key,
307 sender_signature,
308 )?;
309
310 let mut secondary_auths = Vec::with_capacity(secondary_signers.len());
312 for signer in secondary_signers {
313 let signature = signer.sign(&signing_message)?;
314 let public_key = signer.public_key_bytes();
315 secondary_auths.push(make_account_authenticator(
316 signer.signature_scheme(),
317 public_key,
318 signature,
319 )?);
320 }
321
322 let authenticator = TransactionAuthenticator::multi_agent(
323 sender_auth,
324 multi_agent.secondary_signer_addresses.clone(),
325 secondary_auths,
326 );
327
328 Ok(SignedTransaction::new(
329 multi_agent.raw_txn.clone(),
330 authenticator,
331 ))
332}
333
334pub fn sign_fee_payer_transaction<A: Account>(
340 fee_payer_txn: &FeePayerRawTransaction,
341 sender: &A,
342 secondary_signers: &[&dyn Account],
343 fee_payer: &dyn Account,
344) -> AptosResult<SignedTransaction> {
345 let signing_message = fee_payer_txn.signing_message()?;
346
347 let sender_signature = sender.sign(&signing_message)?;
349 let sender_public_key = sender.public_key_bytes();
350 let sender_auth = make_account_authenticator(
351 sender.signature_scheme(),
352 sender_public_key,
353 sender_signature,
354 )?;
355
356 let mut secondary_auths = Vec::with_capacity(secondary_signers.len());
358 for signer in secondary_signers {
359 let signature = signer.sign(&signing_message)?;
360 let public_key = signer.public_key_bytes();
361 secondary_auths.push(make_account_authenticator(
362 signer.signature_scheme(),
363 public_key,
364 signature,
365 )?);
366 }
367
368 let fee_payer_signature = fee_payer.sign(&signing_message)?;
370 let fee_payer_public_key = fee_payer.public_key_bytes();
371 let fee_payer_auth = make_account_authenticator(
372 fee_payer.signature_scheme(),
373 fee_payer_public_key,
374 fee_payer_signature,
375 )?;
376
377 let authenticator = TransactionAuthenticator::fee_payer(
378 sender_auth,
379 fee_payer_txn.secondary_signer_addresses.clone(),
380 secondary_auths,
381 fee_payer_txn.fee_payer_address,
382 fee_payer_auth,
383 );
384
385 Ok(SignedTransaction::new(
386 fee_payer_txn.raw_txn.clone(),
387 authenticator,
388 ))
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394 use crate::transaction::payload::EntryFunction;
395
396 #[test]
397 fn test_builder_missing_fields() {
398 let result = TransactionBuilder::new().build();
399 assert!(result.is_err());
400 }
401
402 #[test]
403 fn test_builder_complete() {
404 let recipient = AccountAddress::from_hex("0x123").unwrap();
405 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
406
407 let txn = TransactionBuilder::new()
408 .sender(AccountAddress::ONE)
409 .sequence_number(0)
410 .payload(payload.into())
411 .chain_id(ChainId::testnet())
412 .build()
413 .unwrap();
414
415 assert_eq!(txn.sender, AccountAddress::ONE);
416 assert_eq!(txn.sequence_number, 0);
417 assert_eq!(txn.max_gas_amount, DEFAULT_MAX_GAS_AMOUNT);
418 assert_eq!(txn.gas_unit_price, DEFAULT_GAS_UNIT_PRICE);
419 }
420
421 #[test]
422 fn test_builder_custom_gas() {
423 let recipient = AccountAddress::from_hex("0x123").unwrap();
424 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
425
426 let txn = TransactionBuilder::new()
427 .sender(AccountAddress::ONE)
428 .sequence_number(0)
429 .payload(payload.into())
430 .max_gas_amount(500_000)
431 .gas_unit_price(200)
432 .chain_id(ChainId::testnet())
433 .build()
434 .unwrap();
435
436 assert_eq!(txn.max_gas_amount, 500_000);
437 assert_eq!(txn.gas_unit_price, 200);
438 }
439
440 #[test]
441 fn test_builder_missing_sender() {
442 let recipient = AccountAddress::from_hex("0x123").unwrap();
443 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
444
445 let result = TransactionBuilder::new()
446 .sequence_number(0)
447 .payload(payload.into())
448 .chain_id(ChainId::testnet())
449 .build();
450
451 assert!(result.is_err());
452 }
453
454 #[test]
455 fn test_builder_missing_payload() {
456 let result = TransactionBuilder::new()
457 .sender(AccountAddress::ONE)
458 .sequence_number(0)
459 .chain_id(ChainId::testnet())
460 .build();
461
462 assert!(result.is_err());
463 }
464
465 #[test]
466 fn test_builder_missing_chain_id() {
467 let recipient = AccountAddress::from_hex("0x123").unwrap();
468 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
469
470 let result = TransactionBuilder::new()
471 .sender(AccountAddress::ONE)
472 .sequence_number(0)
473 .payload(payload.into())
474 .build();
475
476 assert!(result.is_err());
477 }
478
479 #[test]
480 fn test_builder_custom_expiration() {
481 let recipient = AccountAddress::from_hex("0x123").unwrap();
482 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
483 let custom_expiration = 9_999_999_999;
484
485 let txn = TransactionBuilder::new()
486 .sender(AccountAddress::ONE)
487 .sequence_number(0)
488 .payload(payload.into())
489 .expiration_timestamp_secs(custom_expiration)
490 .chain_id(ChainId::testnet())
491 .build()
492 .unwrap();
493
494 assert_eq!(txn.expiration_timestamp_secs, custom_expiration);
495 }
496
497 #[test]
498 fn test_default_expiration() {
499 let now = std::time::SystemTime::now()
501 .duration_since(std::time::UNIX_EPOCH)
502 .unwrap()
503 .as_secs();
504
505 let recipient = AccountAddress::from_hex("0x123").unwrap();
506 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
507
508 let txn = TransactionBuilder::new()
509 .sender(AccountAddress::ONE)
510 .sequence_number(0)
511 .payload(payload.into())
512 .chain_id(ChainId::testnet())
513 .build()
514 .unwrap();
515
516 let expected_min = now + DEFAULT_EXPIRATION_SECONDS - 5;
518 let expected_max = now + DEFAULT_EXPIRATION_SECONDS + 5;
519 assert!(txn.expiration_timestamp_secs >= expected_min);
520 assert!(txn.expiration_timestamp_secs <= expected_max);
521 }
522
523 #[cfg(feature = "ed25519")]
524 #[test]
525 fn test_sign_transaction() {
526 use crate::account::Ed25519Account;
527
528 let account = Ed25519Account::generate();
529 let recipient = AccountAddress::from_hex("0x123").unwrap();
530 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
531
532 let txn = TransactionBuilder::new()
533 .sender(account.address())
534 .sequence_number(0)
535 .payload(payload.into())
536 .chain_id(ChainId::testnet())
537 .build()
538 .unwrap();
539
540 let signed = sign_transaction(&txn, &account).unwrap();
541 assert_eq!(signed.sender(), account.address());
542 }
543
544 #[cfg(feature = "ed25519")]
545 #[test]
546 fn test_sign_multi_agent_transaction() {
547 use crate::account::{Account, Ed25519Account};
548
549 let sender = Ed25519Account::generate();
550 let secondary = Ed25519Account::generate();
551 let recipient = AccountAddress::from_hex("0x123").unwrap();
552 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
553
554 let raw_txn = TransactionBuilder::new()
555 .sender(sender.address())
556 .sequence_number(0)
557 .payload(payload.into())
558 .chain_id(ChainId::testnet())
559 .build()
560 .unwrap();
561
562 let multi_agent = MultiAgentRawTransaction {
563 raw_txn,
564 secondary_signer_addresses: vec![secondary.address()],
565 };
566
567 let secondary_signers: Vec<&dyn Account> = vec![&secondary];
568 let signed =
569 sign_multi_agent_transaction(&multi_agent, &sender, &secondary_signers).unwrap();
570 assert_eq!(signed.sender(), sender.address());
571 }
572
573 #[cfg(feature = "ed25519")]
574 #[test]
575 fn test_sign_fee_payer_transaction() {
576 use crate::account::Ed25519Account;
577
578 let sender = Ed25519Account::generate();
579 let fee_payer = Ed25519Account::generate();
580 let recipient = AccountAddress::from_hex("0x123").unwrap();
581 let payload = EntryFunction::apt_transfer(recipient, 1000).unwrap();
582
583 let raw_txn = TransactionBuilder::new()
584 .sender(sender.address())
585 .sequence_number(0)
586 .payload(payload.into())
587 .chain_id(ChainId::testnet())
588 .build()
589 .unwrap();
590
591 let fee_payer_txn = FeePayerRawTransaction {
592 raw_txn,
593 secondary_signer_addresses: vec![],
594 fee_payer_address: fee_payer.address(),
595 };
596
597 let signed = sign_fee_payer_transaction(&fee_payer_txn, &sender, &[], &fee_payer).unwrap();
598 assert_eq!(signed.sender(), sender.address());
599 }
600
601 #[test]
602 fn test_default_impl() {
603 let builder = TransactionBuilder::default();
604 assert!(builder.sender.is_none());
606 assert!(builder.sequence_number.is_none());
607 assert!(builder.payload.is_none());
608 assert_eq!(builder.max_gas_amount, DEFAULT_MAX_GAS_AMOUNT);
609 assert_eq!(builder.gas_unit_price, DEFAULT_GAS_UNIT_PRICE);
610 }
611}