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