1use super::NotPreparedError;
2
3use async_trait::async_trait;
4use starknet_core::{
5 crypto::compute_hash_on_elements,
6 types::{
7 BlockId, BlockTag, BroadcastedDeployAccountTransactionV3, BroadcastedTransaction,
8 DataAvailabilityMode, DeployAccountTransactionResult, FeeEstimate, Felt, NonZeroFelt,
9 ResourceBounds, ResourceBoundsMapping, SimulatedTransaction, SimulationFlag,
10 SimulationFlagForEstimateFee, StarknetError,
11 },
12};
13use starknet_crypto::PoseidonHasher;
14use starknet_providers::{Provider, ProviderError};
15use std::error::Error;
16
17pub mod argent;
18pub mod open_zeppelin;
19
20const PREFIX_DEPLOY_ACCOUNT: Felt = Felt::from_raw([
22 461298303000467581,
23 18446744073709551615,
24 18443211694809419988,
25 3350261884043292318,
26]);
27
28const QUERY_VERSION_THREE: Felt = Felt::from_raw([
30 576460752142432688,
31 18446744073709551584,
32 17407,
33 18446744073700081569,
34]);
35
36const PREFIX_CONTRACT_ADDRESS: Felt = Felt::from_raw([
38 533439743893157637,
39 8635008616843941496,
40 17289941567720117366,
41 3829237882463328880,
42]);
43
44const ADDR_BOUND: NonZeroFelt = NonZeroFelt::from_raw([
46 576459263475590224,
47 18446744073709255680,
48 160989183,
49 18446743986131443745,
50]);
51
52#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
55#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
56pub trait AccountFactory: Sized {
57 type Provider: Provider + Sync;
59 type SignError: Error + Send + Sync;
61
62 fn class_hash(&self) -> Felt;
64
65 fn calldata(&self) -> Vec<Felt>;
67
68 fn chain_id(&self) -> Felt;
70
71 fn provider(&self) -> &Self::Provider;
73
74 fn is_signer_interactive(&self) -> bool;
81
82 fn block_id(&self) -> BlockId {
84 BlockId::Tag(BlockTag::Latest)
85 }
86
87 async fn sign_deployment_v3(
93 &self,
94 deployment: &RawAccountDeploymentV3,
95 query_only: bool,
96 ) -> Result<Vec<Felt>, Self::SignError>;
97
98 fn deploy_v3(&self, salt: Felt) -> AccountDeploymentV3<'_, Self> {
101 AccountDeploymentV3::new(salt, self)
102 }
103
104 #[deprecated = "transaction version used might change unexpectedly; use `deploy_v3` instead"]
107 fn deploy(&self, salt: Felt) -> AccountDeploymentV3<'_, Self> {
108 self.deploy_v3(salt)
109 }
110}
111
112#[must_use]
118#[derive(Debug)]
119pub struct AccountDeploymentV3<'f, F> {
120 factory: &'f F,
121 salt: Felt,
122 nonce: Option<Felt>,
125 l1_gas: Option<u64>,
126 l1_gas_price: Option<u128>,
127 l2_gas: Option<u64>,
128 l2_gas_price: Option<u128>,
129 l1_data_gas: Option<u64>,
130 l1_data_gas_price: Option<u128>,
131 gas_estimate_multiplier: f64,
132 gas_price_estimate_multiplier: f64,
133 tip: Option<u64>,
134}
135
136#[derive(Debug, Clone)]
138pub struct RawAccountDeploymentV3 {
139 salt: Felt,
140 nonce: Felt,
141 l1_gas: u64,
142 l1_gas_price: u128,
143 l2_gas: u64,
144 l2_gas_price: u128,
145 l1_data_gas: u64,
146 l1_data_gas_price: u128,
147 tip: u64,
148}
149
150#[derive(Debug)]
152pub struct PreparedAccountDeploymentV3<'f, F> {
153 factory: &'f F,
154 inner: RawAccountDeploymentV3,
155}
156
157#[derive(Debug, thiserror::Error)]
159pub enum AccountFactoryError<S> {
160 #[error(transparent)]
162 Signing(S),
163 #[error(transparent)]
165 Provider(ProviderError),
166 #[error("fee calculation overflow")]
168 FeeOutOfRange,
169}
170
171impl<'f, F> AccountDeploymentV3<'f, F> {
172 pub const fn new(salt: Felt, factory: &'f F) -> Self {
177 Self {
178 factory,
179 salt,
180 nonce: None,
181 l1_gas: None,
182 l1_gas_price: None,
183 l2_gas: None,
184 l2_gas_price: None,
185 l1_data_gas: None,
186 l1_data_gas_price: None,
187 gas_estimate_multiplier: 1.5,
188 gas_price_estimate_multiplier: 1.5,
189 tip: None,
190 }
191 }
192
193 pub const fn nonce(self, nonce: Felt) -> Self {
195 Self {
196 nonce: Some(nonce),
197 ..self
198 }
199 }
200
201 pub const fn l1_gas(self, l1_gas: u64) -> Self {
203 Self {
204 l1_gas: Some(l1_gas),
205 ..self
206 }
207 }
208
209 pub const fn l1_gas_price(self, l1_gas_price: u128) -> Self {
211 Self {
212 l1_gas_price: Some(l1_gas_price),
213 ..self
214 }
215 }
216
217 pub const fn l2_gas(self, l2_gas: u64) -> Self {
219 Self {
220 l2_gas: Some(l2_gas),
221 ..self
222 }
223 }
224
225 pub const fn l2_gas_price(self, l2_gas_price: u128) -> Self {
227 Self {
228 l2_gas_price: Some(l2_gas_price),
229 ..self
230 }
231 }
232
233 pub const fn l1_data_gas(self, l1_data_gas: u64) -> Self {
235 Self {
236 l1_data_gas: Some(l1_data_gas),
237 ..self
238 }
239 }
240
241 pub const fn l1_data_gas_price(self, l1_data_gas_price: u128) -> Self {
243 Self {
244 l1_data_gas_price: Some(l1_data_gas_price),
245 ..self
246 }
247 }
248
249 pub const fn gas_estimate_multiplier(self, gas_estimate_multiplier: f64) -> Self {
253 Self {
254 gas_estimate_multiplier,
255 ..self
256 }
257 }
258
259 pub const fn gas_price_estimate_multiplier(self, gas_price_estimate_multiplier: f64) -> Self {
263 Self {
264 gas_price_estimate_multiplier,
265 ..self
266 }
267 }
268
269 pub const fn tip(self, tip: u64) -> Self {
271 Self {
272 tip: Some(tip),
273 ..self
274 }
275 }
276
277 pub fn prepared(self) -> Result<PreparedAccountDeploymentV3<'f, F>, NotPreparedError> {
281 let nonce = self.nonce.ok_or(NotPreparedError)?;
282 let l1_gas = self.l1_gas.ok_or(NotPreparedError)?;
283 let l1_gas_price = self.l1_gas_price.ok_or(NotPreparedError)?;
284 let l2_gas = self.l2_gas.ok_or(NotPreparedError)?;
285 let l2_gas_price = self.l2_gas_price.ok_or(NotPreparedError)?;
286 let l1_data_gas = self.l1_data_gas.ok_or(NotPreparedError)?;
287 let l1_data_gas_price = self.l1_data_gas_price.ok_or(NotPreparedError)?;
288 let tip = self.tip.ok_or(NotPreparedError)?;
289
290 Ok(PreparedAccountDeploymentV3 {
291 factory: self.factory,
292 inner: RawAccountDeploymentV3 {
293 salt: self.salt,
294 nonce,
295 l1_gas,
296 l1_gas_price,
297 l2_gas,
298 l2_gas_price,
299 l1_data_gas,
300 l1_data_gas_price,
301 tip,
302 },
303 })
304 }
305}
306
307impl<'f, F> AccountDeploymentV3<'f, F>
308where
309 F: AccountFactory + Sync,
310{
311 pub fn address(&self) -> Felt {
313 calculate_contract_address(
314 self.salt,
315 self.factory.class_hash(),
316 &self.factory.calldata(),
317 )
318 }
319
320 pub async fn fetch_nonce(&self) -> Result<Felt, ProviderError> {
323 match self
324 .factory
325 .provider()
326 .get_nonce(self.factory.block_id(), self.address())
327 .await
328 {
329 Ok(nonce) => Ok(nonce),
330 Err(ProviderError::StarknetError(StarknetError::ContractNotFound)) => Ok(Felt::ZERO),
331 Err(err) => Err(err),
332 }
333 }
334
335 pub async fn estimate_fee(&self) -> Result<FeeEstimate, AccountFactoryError<F::SignError>> {
337 let nonce = match self.nonce {
339 Some(value) => value,
340 None => self
341 .fetch_nonce()
342 .await
343 .map_err(AccountFactoryError::Provider)?,
344 };
345
346 self.estimate_fee_with_nonce(nonce).await
347 }
348
349 pub async fn simulate(
352 &self,
353 skip_validate: bool,
354 skip_fee_charge: bool,
355 ) -> Result<SimulatedTransaction, AccountFactoryError<F::SignError>> {
356 let nonce = match self.nonce {
358 Some(value) => value,
359 None => self
360 .fetch_nonce()
361 .await
362 .map_err(AccountFactoryError::Provider)?,
363 };
364
365 self.simulate_with_nonce(nonce, skip_validate, skip_fee_charge)
366 .await
367 }
368
369 pub async fn send(
371 &self,
372 ) -> Result<DeployAccountTransactionResult, AccountFactoryError<F::SignError>> {
373 self.prepare().await?.send().await
374 }
375
376 async fn prepare(
377 &self,
378 ) -> Result<PreparedAccountDeploymentV3<'f, F>, AccountFactoryError<F::SignError>> {
379 let nonce = match self.nonce {
381 Some(value) => value,
382 None => self
383 .fetch_nonce()
384 .await
385 .map_err(AccountFactoryError::Provider)?,
386 };
387
388 let (
390 l1_gas,
391 l1_gas_price,
392 l2_gas,
393 l2_gas_price,
394 l1_data_gas,
395 l1_data_gas_price,
396 full_block,
397 ) = match (
398 self.l1_gas,
399 self.l1_gas_price,
400 self.l2_gas,
401 self.l2_gas_price,
402 self.l1_data_gas,
403 self.l1_data_gas_price,
404 ) {
405 (
406 Some(l1_gas),
407 Some(l1_gas_price),
408 Some(l2_gas),
409 Some(l2_gas_price),
410 Some(l1_data_gas),
411 Some(l1_data_gas_price),
412 ) => (
413 l1_gas,
414 l1_gas_price,
415 l2_gas,
416 l2_gas_price,
417 l1_data_gas,
418 l1_data_gas_price,
419 None,
420 ),
421 (Some(l1_gas), _, Some(l2_gas), _, Some(l1_data_gas), _) => {
422 let (block_l1_gas_price, block_l2_gas_price, block_l1_data_gas_price, full_block) =
428 if self.tip.is_some() {
429 let block = self
431 .factory
432 .provider()
433 .get_block_with_tx_hashes(self.factory.block_id())
434 .await
435 .map_err(AccountFactoryError::Provider)?;
436 (
437 block.l1_gas_price().price_in_fri,
438 block.l2_gas_price().price_in_fri,
439 block.l1_data_gas_price().price_in_fri,
440 None,
441 )
442 } else {
443 let block = self
446 .factory
447 .provider()
448 .get_block_with_txs(self.factory.block_id())
449 .await
450 .map_err(AccountFactoryError::Provider)?;
451 (
452 block.l1_gas_price().price_in_fri,
453 block.l2_gas_price().price_in_fri,
454 block.l1_data_gas_price().price_in_fri,
455 Some(block),
456 )
457 };
458
459 let adjusted_l1_gas_price = ((TryInto::<u64>::try_into(block_l1_gas_price)
460 .map_err(|_| AccountFactoryError::FeeOutOfRange)?
461 as f64)
462 * self.gas_price_estimate_multiplier)
463 as u128;
464 let adjusted_l2_gas_price = ((TryInto::<u64>::try_into(block_l2_gas_price)
465 .map_err(|_| AccountFactoryError::FeeOutOfRange)?
466 as f64)
467 * self.gas_price_estimate_multiplier)
468 as u128;
469 let adjusted_l1_data_gas_price = ((TryInto::<u64>::try_into(block_l1_data_gas_price)
470 .map_err(|_| AccountFactoryError::FeeOutOfRange)?
471 as f64)
472 * self.gas_price_estimate_multiplier)
473 as u128;
474
475 (
476 l1_gas,
477 adjusted_l1_gas_price,
478 l2_gas,
479 adjusted_l2_gas_price,
480 l1_data_gas,
481 adjusted_l1_data_gas_price,
482 full_block,
483 )
484 }
485 _ => {
487 let fee_estimate = self.estimate_fee_with_nonce(nonce).await?;
488
489 (
490 ((fee_estimate.l1_gas_consumed as f64) * self.gas_estimate_multiplier) as u64,
491 ((TryInto::<u64>::try_into(fee_estimate.l1_gas_price)
492 .map_err(|_| AccountFactoryError::FeeOutOfRange)?
493 as f64)
494 * self.gas_price_estimate_multiplier) as u128,
495 ((fee_estimate.l2_gas_consumed as f64) * self.gas_estimate_multiplier) as u64,
496 ((TryInto::<u64>::try_into(fee_estimate.l2_gas_price)
497 .map_err(|_| AccountFactoryError::FeeOutOfRange)?
498 as f64)
499 * self.gas_price_estimate_multiplier) as u128,
500 ((fee_estimate.l1_data_gas_consumed as f64) * self.gas_estimate_multiplier)
501 as u64,
502 ((TryInto::<u64>::try_into(fee_estimate.l1_data_gas_price)
503 .map_err(|_| AccountFactoryError::FeeOutOfRange)?
504 as f64)
505 * self.gas_price_estimate_multiplier) as u128,
506 None,
507 )
508 }
509 };
510
511 let tip = match self.tip {
512 Some(tip) => tip,
513 None => {
514 let block = match full_block {
516 Some(block) => block,
517 None => self
518 .factory
519 .provider()
520 .get_block_with_txs(self.factory.block_id())
521 .await
522 .map_err(AccountFactoryError::Provider)?,
523 };
524 block.median_tip()
525 }
526 };
527
528 Ok(PreparedAccountDeploymentV3 {
529 factory: self.factory,
530 inner: RawAccountDeploymentV3 {
531 salt: self.salt,
532 nonce,
533 l1_gas,
534 l1_gas_price,
535 l2_gas,
536 l2_gas_price,
537 l1_data_gas,
538 l1_data_gas_price,
539 tip,
540 },
541 })
542 }
543
544 async fn estimate_fee_with_nonce(
545 &self,
546 nonce: Felt,
547 ) -> Result<FeeEstimate, AccountFactoryError<F::SignError>> {
548 let skip_signature = self.factory.is_signer_interactive();
549
550 let prepared = PreparedAccountDeploymentV3 {
551 factory: self.factory,
552 inner: RawAccountDeploymentV3 {
553 salt: self.salt,
554 nonce,
555 l1_gas: 0,
556 l1_gas_price: 0,
557 l2_gas: 0,
558 l2_gas_price: 0,
559 l1_data_gas: 0,
560 l1_data_gas_price: 0,
561 tip: 0,
562 },
563 };
564 let deploy = prepared
565 .get_deploy_request(true, skip_signature)
566 .await
567 .map_err(AccountFactoryError::Signing)?;
568
569 self.factory
570 .provider()
571 .estimate_fee_single(
572 BroadcastedTransaction::DeployAccount(deploy),
573 if skip_signature {
574 vec![SimulationFlagForEstimateFee::SkipValidate]
576 } else {
577 vec![]
579 },
580 self.factory.block_id(),
581 )
582 .await
583 .map_err(AccountFactoryError::Provider)
584 }
585
586 async fn simulate_with_nonce(
587 &self,
588 nonce: Felt,
589 skip_validate: bool,
590 skip_fee_charge: bool,
591 ) -> Result<SimulatedTransaction, AccountFactoryError<F::SignError>> {
592 let skip_signature = if self.factory.is_signer_interactive() {
593 skip_validate
597 } else {
598 false
600 };
601
602 let prepared = PreparedAccountDeploymentV3 {
603 factory: self.factory,
604 inner: RawAccountDeploymentV3 {
605 salt: self.salt,
606 nonce,
607 l1_gas: self.l1_gas.unwrap_or_default(),
608 l1_gas_price: self.l1_gas_price.unwrap_or_default(),
609 l2_gas: self.l2_gas.unwrap_or_default(),
610 l2_gas_price: self.l2_gas_price.unwrap_or_default(),
611 l1_data_gas: self.l1_data_gas.unwrap_or_default(),
612 l1_data_gas_price: self.l1_data_gas_price.unwrap_or_default(),
613 tip: self.tip.unwrap_or_default(),
614 },
615 };
616 let deploy = prepared
617 .get_deploy_request(true, skip_signature)
618 .await
619 .map_err(AccountFactoryError::Signing)?;
620
621 let mut flags = vec![];
622
623 if skip_validate {
624 flags.push(SimulationFlag::SkipValidate);
625 }
626 if skip_fee_charge {
627 flags.push(SimulationFlag::SkipFeeCharge);
628 }
629
630 self.factory
631 .provider()
632 .simulate_transaction(
633 self.factory.block_id(),
634 BroadcastedTransaction::DeployAccount(deploy),
635 &flags,
636 )
637 .await
638 .map_err(AccountFactoryError::Provider)
639 }
640}
641
642impl RawAccountDeploymentV3 {
643 pub const fn salt(&self) -> Felt {
645 self.salt
646 }
647
648 pub const fn nonce(&self) -> Felt {
650 self.nonce
651 }
652
653 pub const fn l1_gas(&self) -> u64 {
655 self.l1_gas
656 }
657
658 pub const fn l1_gas_price(&self) -> u128 {
660 self.l1_gas_price
661 }
662
663 pub const fn l2_gas(&self) -> u64 {
665 self.l2_gas
666 }
667
668 pub const fn l2_gas_price(&self) -> u128 {
670 self.l2_gas_price
671 }
672
673 pub const fn l1_data_gas(&self) -> u64 {
675 self.l1_data_gas
676 }
677
678 pub const fn l1_data_gas_price(&self) -> u128 {
680 self.l1_data_gas_price
681 }
682
683 pub const fn tip(&self) -> u64 {
685 self.tip
686 }
687}
688
689impl<'f, F> PreparedAccountDeploymentV3<'f, F> {
690 pub const fn from_raw(raw_deployment: RawAccountDeploymentV3, factory: &'f F) -> Self {
693 Self {
694 factory,
695 inner: raw_deployment,
696 }
697 }
698}
699
700impl<F> PreparedAccountDeploymentV3<'_, F>
701where
702 F: AccountFactory,
703{
704 pub fn address(&self) -> Felt {
706 calculate_contract_address(
707 self.inner.salt,
708 self.factory.class_hash(),
709 &self.factory.calldata(),
710 )
711 }
712
713 pub fn transaction_hash(&self, query_only: bool) -> Felt {
715 let mut hasher = PoseidonHasher::new();
716
717 hasher.update(PREFIX_DEPLOY_ACCOUNT);
718 hasher.update(if query_only {
719 QUERY_VERSION_THREE
720 } else {
721 Felt::THREE
722 });
723 hasher.update(self.address());
724
725 hasher.update({
726 let mut fee_hasher = PoseidonHasher::new();
727
728 fee_hasher.update(self.inner.tip.into());
729
730 let mut resource_buffer = [
731 0, 0, b'L', b'1', b'_', b'G', b'A', b'S', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
732 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
733 ];
734 resource_buffer[8..(8 + 8)].copy_from_slice(&self.inner.l1_gas.to_be_bytes());
735 resource_buffer[(8 + 8)..].copy_from_slice(&self.inner.l1_gas_price.to_be_bytes());
736 fee_hasher.update(Felt::from_bytes_be(&resource_buffer));
737
738 let mut resource_buffer = [
739 0, 0, b'L', b'2', b'_', b'G', b'A', b'S', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
740 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
741 ];
742 resource_buffer[8..(8 + 8)].copy_from_slice(&self.inner.l2_gas.to_be_bytes());
743 resource_buffer[(8 + 8)..].copy_from_slice(&self.inner.l2_gas_price.to_be_bytes());
744 fee_hasher.update(Felt::from_bytes_be(&resource_buffer));
745
746 let mut resource_buffer = [
747 0, b'L', b'1', b'_', b'D', b'A', b'T', b'A', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
748 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
749 ];
750 resource_buffer[8..(8 + 8)].copy_from_slice(&self.inner.l1_data_gas.to_be_bytes());
751 resource_buffer[(8 + 8)..].copy_from_slice(&self.inner.l1_data_gas_price.to_be_bytes());
752 fee_hasher.update(Felt::from_bytes_be(&resource_buffer));
753
754 fee_hasher.finalize()
755 });
756
757 hasher.update(PoseidonHasher::new().finalize());
759
760 hasher.update(self.factory.chain_id());
761 hasher.update(self.inner.nonce);
762
763 hasher.update(Felt::ZERO);
765
766 hasher.update({
767 let mut calldata_hasher = PoseidonHasher::new();
768
769 self.factory
770 .calldata()
771 .into_iter()
772 .for_each(|element| calldata_hasher.update(element));
773
774 calldata_hasher.finalize()
775 });
776
777 hasher.update(self.factory.class_hash());
778 hasher.update(self.inner.salt);
779
780 hasher.finalize()
781 }
782
783 pub async fn send(
785 &self,
786 ) -> Result<DeployAccountTransactionResult, AccountFactoryError<F::SignError>> {
787 let tx_request = self
788 .get_deploy_request(false, false)
789 .await
790 .map_err(AccountFactoryError::Signing)?;
791 self.factory
792 .provider()
793 .add_deploy_account_transaction(tx_request)
794 .await
795 .map_err(AccountFactoryError::Provider)
796 }
797
798 pub async fn get_deploy_request(
800 &self,
801 query_only: bool,
802 skip_signature: bool,
803 ) -> Result<BroadcastedDeployAccountTransactionV3, F::SignError> {
804 Ok(BroadcastedDeployAccountTransactionV3 {
805 signature: if skip_signature {
806 vec![]
807 } else {
808 self.factory
809 .sign_deployment_v3(&self.inner, query_only)
810 .await?
811 },
812 nonce: self.inner.nonce,
813 contract_address_salt: self.inner.salt,
814 constructor_calldata: self.factory.calldata(),
815 class_hash: self.factory.class_hash(),
816 resource_bounds: ResourceBoundsMapping {
817 l1_gas: ResourceBounds {
818 max_amount: self.inner.l1_gas,
819 max_price_per_unit: self.inner.l1_gas_price,
820 },
821 l1_data_gas: ResourceBounds {
822 max_amount: self.inner.l1_data_gas,
823 max_price_per_unit: self.inner.l1_data_gas_price,
824 },
825 l2_gas: ResourceBounds {
826 max_amount: self.inner.l2_gas,
827 max_price_per_unit: self.inner.l2_gas_price,
828 },
829 },
830 tip: self.inner.tip,
831 paymaster_data: vec![],
833 nonce_data_availability_mode: DataAvailabilityMode::L1,
835 fee_data_availability_mode: DataAvailabilityMode::L1,
836 is_query: query_only,
837 })
838 }
839}
840
841fn calculate_contract_address(salt: Felt, class_hash: Felt, constructor_calldata: &[Felt]) -> Felt {
842 compute_hash_on_elements(&[
843 PREFIX_CONTRACT_ADDRESS,
844 Felt::ZERO,
845 salt,
846 class_hash,
847 compute_hash_on_elements(constructor_calldata),
848 ])
849 .mod_floor(&ADDR_BOUND)
850}