1use super::{
2 super::NotPreparedError, Account, AccountError, ConnectedAccount, ExecutionV3,
3 PreparedExecutionV3, RawExecutionV3,
4};
5use crate::ExecutionEncoder;
6
7use starknet_core::types::{
8 BroadcastedInvokeTransactionV3, BroadcastedTransaction, Call, DataAvailabilityMode,
9 FeeEstimate, Felt, InvokeTransactionResult, ResourceBounds, ResourceBoundsMapping,
10 SimulatedTransaction, SimulationFlag, SimulationFlagForEstimateFee,
11};
12use starknet_crypto::PoseidonHasher;
13use starknet_providers::Provider;
14use starknet_signers::SignerInteractivityContext;
15
16const PREFIX_INVOKE: Felt = Felt::from_raw([
18 513398556346534256,
19 18446744073709551615,
20 18446744073709551615,
21 18443034532770911073,
22]);
23
24const QUERY_VERSION_THREE: Felt = Felt::from_raw([
26 576460752142432688,
27 18446744073709551584,
28 17407,
29 18446744073700081569,
30]);
31
32impl<'a, A> ExecutionV3<'a, A> {
33 pub const fn new(calls: Vec<Call>, account: &'a A) -> Self {
38 Self {
39 account,
40 calls,
41 nonce: None,
42 l1_gas: None,
43 l1_gas_price: None,
44 l2_gas: None,
45 l2_gas_price: None,
46 l1_data_gas: None,
47 l1_data_gas_price: None,
48 gas_estimate_multiplier: 1.5,
49 gas_price_estimate_multiplier: 1.5,
50 tip: None,
51 }
52 }
53
54 pub fn nonce(self, nonce: Felt) -> Self {
56 Self {
57 nonce: Some(nonce),
58 ..self
59 }
60 }
61
62 pub fn l1_gas(self, l1_gas: u64) -> Self {
64 Self {
65 l1_gas: Some(l1_gas),
66 ..self
67 }
68 }
69
70 pub fn l1_gas_price(self, l1_gas_price: u128) -> Self {
72 Self {
73 l1_gas_price: Some(l1_gas_price),
74 ..self
75 }
76 }
77
78 pub fn l2_gas(self, l2_gas: u64) -> Self {
80 Self {
81 l2_gas: Some(l2_gas),
82 ..self
83 }
84 }
85
86 pub fn l2_gas_price(self, l2_gas_price: u128) -> Self {
88 Self {
89 l2_gas_price: Some(l2_gas_price),
90 ..self
91 }
92 }
93
94 pub fn l1_data_gas(self, l1_data_gas: u64) -> Self {
96 Self {
97 l1_data_gas: Some(l1_data_gas),
98 ..self
99 }
100 }
101
102 pub fn l1_data_gas_price(self, l1_data_gas_price: u128) -> Self {
104 Self {
105 l1_data_gas_price: Some(l1_data_gas_price),
106 ..self
107 }
108 }
109
110 pub fn gas_estimate_multiplier(self, gas_estimate_multiplier: f64) -> Self {
114 Self {
115 gas_estimate_multiplier,
116 ..self
117 }
118 }
119
120 pub fn gas_price_estimate_multiplier(self, gas_price_estimate_multiplier: f64) -> Self {
124 Self {
125 gas_price_estimate_multiplier,
126 ..self
127 }
128 }
129
130 pub fn tip(self, tip: u64) -> Self {
132 Self {
133 tip: Some(tip),
134 ..self
135 }
136 }
137
138 pub fn prepared(self) -> Result<PreparedExecutionV3<'a, A>, NotPreparedError> {
141 let nonce = self.nonce.ok_or(NotPreparedError)?;
142 let l1_gas = self.l1_gas.ok_or(NotPreparedError)?;
143 let l1_gas_price = self.l1_gas_price.ok_or(NotPreparedError)?;
144 let l2_gas = self.l2_gas.ok_or(NotPreparedError)?;
145 let l2_gas_price = self.l2_gas_price.ok_or(NotPreparedError)?;
146 let l1_data_gas = self.l1_data_gas.ok_or(NotPreparedError)?;
147 let l1_data_gas_price = self.l1_data_gas_price.ok_or(NotPreparedError)?;
148 let tip = self.tip.ok_or(NotPreparedError)?;
149
150 Ok(PreparedExecutionV3 {
151 account: self.account,
152 inner: RawExecutionV3 {
153 calls: self.calls,
154 nonce,
155 l1_gas,
156 l1_gas_price,
157 l2_gas,
158 l2_gas_price,
159 l1_data_gas,
160 l1_data_gas_price,
161 tip,
162 },
163 })
164 }
165}
166
167impl<'a, A> ExecutionV3<'a, A>
168where
169 A: ConnectedAccount + Sync,
170{
171 pub async fn estimate_fee(&self) -> Result<FeeEstimate, AccountError<A::SignError>> {
173 let nonce = match self.nonce {
175 Some(value) => value,
176 None => self
177 .account
178 .get_nonce()
179 .await
180 .map_err(AccountError::Provider)?,
181 };
182
183 self.estimate_fee_with_nonce(nonce).await
184 }
185
186 pub async fn simulate(
189 &self,
190 skip_validate: bool,
191 skip_fee_charge: bool,
192 ) -> Result<SimulatedTransaction, AccountError<A::SignError>> {
193 let nonce = match self.nonce {
195 Some(value) => value,
196 None => self
197 .account
198 .get_nonce()
199 .await
200 .map_err(AccountError::Provider)?,
201 };
202
203 self.simulate_with_nonce(nonce, skip_validate, skip_fee_charge)
204 .await
205 }
206
207 pub async fn send(&self) -> Result<InvokeTransactionResult, AccountError<A::SignError>> {
209 self.prepare().await?.send().await
210 }
211
212 async fn prepare(&self) -> Result<PreparedExecutionV3<'a, A>, AccountError<A::SignError>> {
213 let nonce = match self.nonce {
215 Some(value) => value,
216 None => self
217 .account
218 .get_nonce()
219 .await
220 .map_err(AccountError::Provider)?,
221 };
222
223 let (
225 l1_gas,
226 l1_gas_price,
227 l2_gas,
228 l2_gas_price,
229 l1_data_gas,
230 l1_data_gas_price,
231 full_block,
232 ) = match (
233 self.l1_gas,
234 self.l1_gas_price,
235 self.l2_gas,
236 self.l2_gas_price,
237 self.l1_data_gas,
238 self.l1_data_gas_price,
239 ) {
240 (
241 Some(l1_gas),
242 Some(l1_gas_price),
243 Some(l2_gas),
244 Some(l2_gas_price),
245 Some(l1_data_gas),
246 Some(l1_data_gas_price),
247 ) => (
248 l1_gas,
249 l1_gas_price,
250 l2_gas,
251 l2_gas_price,
252 l1_data_gas,
253 l1_data_gas_price,
254 None,
255 ),
256 (Some(l1_gas), _, Some(l2_gas), _, Some(l1_data_gas), _) => {
257 let (block_l1_gas_price, block_l2_gas_price, block_l1_data_gas_price, full_block) =
263 if self.tip.is_some() {
264 let block = self
266 .account
267 .provider()
268 .get_block_with_tx_hashes(self.account.block_id())
269 .await
270 .map_err(AccountError::Provider)?;
271 (
272 block.l1_gas_price().price_in_fri,
273 block.l2_gas_price().price_in_fri,
274 block.l1_data_gas_price().price_in_fri,
275 None,
276 )
277 } else {
278 let block = self
281 .account
282 .provider()
283 .get_block_with_txs(self.account.block_id())
284 .await
285 .map_err(AccountError::Provider)?;
286 (
287 block.l1_gas_price().price_in_fri,
288 block.l2_gas_price().price_in_fri,
289 block.l1_data_gas_price().price_in_fri,
290 Some(block),
291 )
292 };
293
294 let adjusted_l1_gas_price =
295 ((TryInto::<u64>::try_into(block_l1_gas_price)
296 .map_err(|_| AccountError::FeeOutOfRange)? as f64)
297 * self.gas_price_estimate_multiplier) as u128;
298 let adjusted_l2_gas_price =
299 ((TryInto::<u64>::try_into(block_l2_gas_price)
300 .map_err(|_| AccountError::FeeOutOfRange)? as f64)
301 * self.gas_price_estimate_multiplier) as u128;
302 let adjusted_l1_data_gas_price =
303 ((TryInto::<u64>::try_into(block_l1_data_gas_price)
304 .map_err(|_| AccountError::FeeOutOfRange)? as f64)
305 * self.gas_price_estimate_multiplier) as u128;
306
307 (
308 l1_gas,
309 adjusted_l1_gas_price,
310 l2_gas,
311 adjusted_l2_gas_price,
312 l1_data_gas,
313 adjusted_l1_data_gas_price,
314 full_block,
315 )
316 }
317 _ => {
319 let fee_estimate = self.estimate_fee_with_nonce(nonce).await?;
320
321 (
322 ((fee_estimate.l1_gas_consumed as f64) * self.gas_estimate_multiplier) as u64,
323 ((TryInto::<u64>::try_into(fee_estimate.l1_gas_price)
324 .map_err(|_| AccountError::FeeOutOfRange)? as f64)
325 * self.gas_price_estimate_multiplier) as u128,
326 ((fee_estimate.l2_gas_consumed as f64) * self.gas_estimate_multiplier) as u64,
327 ((TryInto::<u64>::try_into(fee_estimate.l2_gas_price)
328 .map_err(|_| AccountError::FeeOutOfRange)? as f64)
329 * self.gas_price_estimate_multiplier) as u128,
330 ((fee_estimate.l1_data_gas_consumed as f64) * self.gas_estimate_multiplier)
331 as u64,
332 ((TryInto::<u64>::try_into(fee_estimate.l1_data_gas_price)
333 .map_err(|_| AccountError::FeeOutOfRange)? as f64)
334 * self.gas_price_estimate_multiplier) as u128,
335 None,
336 )
337 }
338 };
339
340 let tip = match self.tip {
341 Some(tip) => tip,
342 None => {
343 let block = match full_block {
345 Some(block) => block,
346 None => self
347 .account
348 .provider()
349 .get_block_with_txs(self.account.block_id())
350 .await
351 .map_err(AccountError::Provider)?,
352 };
353 block.median_tip()
354 }
355 };
356
357 Ok(PreparedExecutionV3 {
358 account: self.account,
359 inner: RawExecutionV3 {
360 calls: self.calls.clone(),
361 nonce,
362 l1_gas,
363 l1_gas_price,
364 l2_gas,
365 l2_gas_price,
366 l1_data_gas,
367 l1_data_gas_price,
368 tip,
369 },
370 })
371 }
372
373 async fn estimate_fee_with_nonce(
374 &self,
375 nonce: Felt,
376 ) -> Result<FeeEstimate, AccountError<A::SignError>> {
377 let skip_signature = self
378 .account
379 .is_signer_interactive(SignerInteractivityContext::Execution { calls: &self.calls });
380
381 let prepared = PreparedExecutionV3 {
382 account: self.account,
383 inner: RawExecutionV3 {
384 calls: self.calls.clone(),
385 nonce,
386 l1_gas: 0,
387 l1_gas_price: 0,
388 l2_gas: 0,
389 l2_gas_price: 0,
390 l1_data_gas: 0,
391 l1_data_gas_price: 0,
392 tip: 0,
393 },
394 };
395 let invoke = prepared
396 .get_invoke_request(true, skip_signature)
397 .await
398 .map_err(AccountError::Signing)?;
399
400 self.account
401 .provider()
402 .estimate_fee_single(
403 BroadcastedTransaction::Invoke(invoke),
404 if skip_signature {
405 vec![SimulationFlagForEstimateFee::SkipValidate]
407 } else {
408 vec![]
410 },
411 self.account.block_id(),
412 )
413 .await
414 .map_err(AccountError::Provider)
415 }
416
417 async fn simulate_with_nonce(
418 &self,
419 nonce: Felt,
420 skip_validate: bool,
421 skip_fee_charge: bool,
422 ) -> Result<SimulatedTransaction, AccountError<A::SignError>> {
423 let skip_signature = if self
424 .account
425 .is_signer_interactive(SignerInteractivityContext::Execution { calls: &self.calls })
426 {
427 skip_validate
431 } else {
432 false
434 };
435
436 let prepared = PreparedExecutionV3 {
437 account: self.account,
438 inner: RawExecutionV3 {
439 calls: self.calls.clone(),
440 nonce,
441 l1_gas: self.l1_gas.unwrap_or_default(),
442 l1_gas_price: self.l1_gas_price.unwrap_or_default(),
443 l2_gas: self.l2_gas.unwrap_or_default(),
444 l2_gas_price: self.l2_gas_price.unwrap_or_default(),
445 l1_data_gas: self.l1_data_gas.unwrap_or_default(),
446 l1_data_gas_price: self.l1_data_gas_price.unwrap_or_default(),
447 tip: self.tip.unwrap_or_default(),
448 },
449 };
450 let invoke = prepared
451 .get_invoke_request(true, skip_signature)
452 .await
453 .map_err(AccountError::Signing)?;
454
455 let mut flags = vec![];
456
457 if skip_validate {
458 flags.push(SimulationFlag::SkipValidate);
459 }
460 if skip_fee_charge {
461 flags.push(SimulationFlag::SkipFeeCharge);
462 }
463
464 self.account
465 .provider()
466 .simulate_transaction(
467 self.account.block_id(),
468 BroadcastedTransaction::Invoke(invoke),
469 &flags,
470 )
471 .await
472 .map_err(AccountError::Provider)
473 }
474}
475
476impl RawExecutionV3 {
477 pub fn transaction_hash<E>(
479 &self,
480 chain_id: Felt,
481 address: Felt,
482 query_only: bool,
483 encoder: E,
484 ) -> Felt
485 where
486 E: ExecutionEncoder,
487 {
488 let mut hasher = PoseidonHasher::new();
489
490 hasher.update(PREFIX_INVOKE);
491 hasher.update(if query_only {
492 QUERY_VERSION_THREE
493 } else {
494 Felt::THREE
495 });
496 hasher.update(address);
497
498 hasher.update({
499 let mut fee_hasher = PoseidonHasher::new();
500
501 fee_hasher.update(self.tip.into());
502
503 let mut resource_buffer = [
504 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,
505 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
506 ];
507 resource_buffer[8..(8 + 8)].copy_from_slice(&self.l1_gas.to_be_bytes());
508 resource_buffer[(8 + 8)..].copy_from_slice(&self.l1_gas_price.to_be_bytes());
509 fee_hasher.update(Felt::from_bytes_be(&resource_buffer));
510
511 let mut resource_buffer = [
512 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,
513 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
514 ];
515 resource_buffer[8..(8 + 8)].copy_from_slice(&self.l2_gas.to_be_bytes());
516 resource_buffer[(8 + 8)..].copy_from_slice(&self.l2_gas_price.to_be_bytes());
517 fee_hasher.update(Felt::from_bytes_be(&resource_buffer));
518
519 let mut resource_buffer = [
520 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,
521 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
522 ];
523 resource_buffer[8..(8 + 8)].copy_from_slice(&self.l1_data_gas.to_be_bytes());
524 resource_buffer[(8 + 8)..].copy_from_slice(&self.l1_data_gas_price.to_be_bytes());
525 fee_hasher.update(Felt::from_bytes_be(&resource_buffer));
526
527 fee_hasher.finalize()
528 });
529
530 hasher.update(PoseidonHasher::new().finalize());
532
533 hasher.update(chain_id);
534 hasher.update(self.nonce);
535
536 hasher.update(Felt::ZERO);
538
539 hasher.update(PoseidonHasher::new().finalize());
541
542 hasher.update({
543 let mut calldata_hasher = PoseidonHasher::new();
544
545 encoder
546 .encode_calls(&self.calls)
547 .into_iter()
548 .for_each(|element| calldata_hasher.update(element));
549
550 calldata_hasher.finalize()
551 });
552
553 hasher.finalize()
554 }
555
556 pub fn calls(&self) -> &[Call] {
558 &self.calls
559 }
560
561 pub const fn nonce(&self) -> Felt {
563 self.nonce
564 }
565
566 pub const fn l1_gas(&self) -> u64 {
568 self.l1_gas
569 }
570
571 pub const fn l1_gas_price(&self) -> u128 {
573 self.l1_gas_price
574 }
575
576 pub const fn l2_gas(&self) -> u64 {
578 self.l2_gas
579 }
580
581 pub const fn l2_gas_price(&self) -> u128 {
583 self.l2_gas_price
584 }
585
586 pub const fn l1_data_gas(&self) -> u64 {
588 self.l1_data_gas
589 }
590
591 pub const fn l1_data_gas_price(&self) -> u128 {
593 self.l1_data_gas_price
594 }
595
596 pub const fn tip(&self) -> u64 {
598 self.tip
599 }
600}
601
602impl<A> PreparedExecutionV3<'_, A>
603where
604 A: Account,
605{
606 pub fn transaction_hash(&self, query_only: bool) -> Felt {
609 self.inner.transaction_hash(
610 self.account.chain_id(),
611 self.account.address(),
612 query_only,
613 self.account,
614 )
615 }
616}
617
618impl<A> PreparedExecutionV3<'_, A>
619where
620 A: ConnectedAccount,
621{
622 pub async fn send(&self) -> Result<InvokeTransactionResult, AccountError<A::SignError>> {
624 let tx_request = self
625 .get_invoke_request(false, false)
626 .await
627 .map_err(AccountError::Signing)?;
628 self.account
629 .provider()
630 .add_invoke_transaction(tx_request)
631 .await
632 .map_err(AccountError::Provider)
633 }
634
635 pub async fn get_invoke_request(
640 &self,
641 query_only: bool,
642 skip_signature: bool,
643 ) -> Result<BroadcastedInvokeTransactionV3, A::SignError> {
644 Ok(BroadcastedInvokeTransactionV3 {
645 sender_address: self.account.address(),
646 calldata: self.account.encode_calls(&self.inner.calls),
647 signature: if skip_signature {
648 vec![]
649 } else {
650 self.account
651 .sign_execution_v3(&self.inner, query_only)
652 .await?
653 },
654 nonce: self.inner.nonce,
655 resource_bounds: ResourceBoundsMapping {
656 l1_gas: ResourceBounds {
657 max_amount: self.inner.l1_gas,
658 max_price_per_unit: self.inner.l1_gas_price,
659 },
660 l1_data_gas: ResourceBounds {
661 max_amount: self.inner.l1_data_gas,
662 max_price_per_unit: self.inner.l1_data_gas_price,
663 },
664 l2_gas: ResourceBounds {
665 max_amount: self.inner.l2_gas,
666 max_price_per_unit: self.inner.l2_gas_price,
667 },
668 },
669 tip: self.inner.tip,
670 paymaster_data: vec![],
672 account_deployment_data: vec![],
674 nonce_data_availability_mode: DataAvailabilityMode::L1,
676 fee_data_availability_mode: DataAvailabilityMode::L1,
677 is_query: query_only,
678 })
679 }
680}