1use starknet_rust_accounts::{Account, AccountError, ConnectedAccount, ExecutionV3};
2use starknet_rust_core::{
3 types::{Call, FeeEstimate, Felt, InvokeTransactionResult, SimulatedTransaction},
4 utils::{get_udc_deployed_address, UdcUniqueSettings, UdcUniqueness},
5};
6
7const LEGACY_UDC_ADDRESS: Felt = Felt::from_raw([
9 121672436446604875,
10 9333317513348225193,
11 15685625669053253235,
12 15144800532519055890,
13]);
14
15const NEW_UDC_ADDRESS: Felt = Felt::from_raw([
17 505287751652144584,
18 6849092491656713429,
19 14735209673864872887,
20 4208494925911946768,
21]);
22
23const SELECTOR_DEPLOYCONTRACT: Felt = Felt::from_raw([
25 469988280392664069,
26 1439621915307882061,
27 1265649739554438882,
28 18249998464715511309,
29]);
30
31#[derive(Debug)]
34pub struct ContractFactory<A> {
35 class_hash: Felt,
36 udc_address: Felt,
37 account: A,
38}
39
40#[must_use]
43#[derive(Debug)]
44pub struct DeploymentV3<'f, A> {
45 factory: &'f ContractFactory<A>,
46 constructor_calldata: Vec<Felt>,
47 salt: Felt,
48 unique: bool,
49 nonce: Option<Felt>,
51 l1_gas: Option<u64>,
52 l1_gas_price: Option<u128>,
53 l2_gas: Option<u64>,
54 l2_gas_price: Option<u128>,
55 l1_data_gas: Option<u64>,
56 l1_data_gas_price: Option<u128>,
57 gas_estimate_multiplier: f64,
58 gas_price_estimate_multiplier: f64,
59 tip: Option<u64>,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
64pub enum UdcSelector {
65 Legacy,
68 #[default]
71 New,
72 Custom(Felt),
74}
75
76impl<A> ContractFactory<A> {
77 #[deprecated = "this method uses the legacy UDC; use `new_with_udc` instead"]
82 pub const fn new(class_hash: Felt, account: A) -> Self {
83 Self::new_with_udc(class_hash, account, UdcSelector::Legacy)
84 }
85
86 pub const fn new_with_udc(class_hash: Felt, account: A, udc: UdcSelector) -> Self {
88 Self {
89 class_hash,
90 udc_address: udc.address(),
91 account,
92 }
93 }
94}
95
96impl<A> ContractFactory<A>
97where
98 A: Account,
99{
100 pub const fn deploy_v3(
103 &self,
104 constructor_calldata: Vec<Felt>,
105 salt: Felt,
106 unique: bool,
107 ) -> DeploymentV3<'_, A> {
108 DeploymentV3 {
109 factory: self,
110 constructor_calldata,
111 salt,
112 unique,
113 nonce: None,
114 l1_gas: None,
115 l1_gas_price: None,
116 l2_gas: None,
117 l2_gas_price: None,
118 l1_data_gas: None,
119 l1_data_gas_price: None,
120 gas_estimate_multiplier: 1.5,
121 gas_price_estimate_multiplier: 1.5,
122 tip: None,
123 }
124 }
125
126 #[deprecated = "transaction version used might change unexpectedly; use `deploy_v3` instead"]
129 pub const fn deploy(
130 &self,
131 constructor_calldata: Vec<Felt>,
132 salt: Felt,
133 unique: bool,
134 ) -> DeploymentV3<'_, A> {
135 self.deploy_v3(constructor_calldata, salt, unique)
136 }
137}
138
139impl<A> DeploymentV3<'_, A> {
140 pub fn nonce(self, nonce: Felt) -> Self {
142 Self {
143 nonce: Some(nonce),
144 ..self
145 }
146 }
147
148 pub fn l1_gas(self, l1_gas: u64) -> Self {
150 Self {
151 l1_gas: Some(l1_gas),
152 ..self
153 }
154 }
155
156 pub fn l1_gas_price(self, l1_gas_price: u128) -> Self {
158 Self {
159 l1_gas_price: Some(l1_gas_price),
160 ..self
161 }
162 }
163
164 pub fn l2_gas(self, l2_gas: u64) -> Self {
166 Self {
167 l2_gas: Some(l2_gas),
168 ..self
169 }
170 }
171
172 pub fn l2_gas_price(self, l2_gas_price: u128) -> Self {
174 Self {
175 l2_gas_price: Some(l2_gas_price),
176 ..self
177 }
178 }
179
180 pub fn l1_data_gas(self, l1_data_gas: u64) -> Self {
182 Self {
183 l1_data_gas: Some(l1_data_gas),
184 ..self
185 }
186 }
187
188 pub fn l1_data_gas_price(self, l1_data_gas_price: u128) -> Self {
190 Self {
191 l1_data_gas_price: Some(l1_data_gas_price),
192 ..self
193 }
194 }
195
196 pub fn gas_estimate_multiplier(self, gas_estimate_multiplier: f64) -> Self {
200 Self {
201 gas_estimate_multiplier,
202 ..self
203 }
204 }
205
206 pub fn gas_price_estimate_multiplier(self, gas_price_estimate_multiplier: f64) -> Self {
210 Self {
211 gas_price_estimate_multiplier,
212 ..self
213 }
214 }
215
216 pub fn tip(self, tip: u64) -> Self {
218 Self {
219 tip: Some(tip),
220 ..self
221 }
222 }
223}
224
225impl<A> DeploymentV3<'_, A>
226where
227 A: Account,
228{
229 pub fn deployed_address(&self) -> Felt {
231 get_udc_deployed_address(
232 self.salt,
233 self.factory.class_hash,
234 &if self.unique {
235 UdcUniqueness::Unique(UdcUniqueSettings {
236 deployer_address: self.factory.account.address(),
237 udc_contract_address: self.factory.udc_address,
238 })
239 } else {
240 UdcUniqueness::NotUnique
241 },
242 &self.constructor_calldata,
243 )
244 }
245}
246
247impl<A> DeploymentV3<'_, A>
248where
249 A: ConnectedAccount + Sync,
250{
251 pub async fn estimate_fee(&self) -> Result<FeeEstimate, AccountError<A::SignError>> {
253 let execution: ExecutionV3<'_, A> = self.into();
254 execution.estimate_fee().await
255 }
256
257 pub async fn simulate(
260 &self,
261 skip_validate: bool,
262 skip_fee_charge: bool,
263 ) -> Result<SimulatedTransaction, AccountError<A::SignError>> {
264 let execution: ExecutionV3<'_, A> = self.into();
265 execution.simulate(skip_validate, skip_fee_charge).await
266 }
267
268 pub async fn send(&self) -> Result<InvokeTransactionResult, AccountError<A::SignError>> {
270 let execution: ExecutionV3<'_, A> = self.into();
271 execution.send().await
272 }
273}
274
275impl<'f, A> From<&DeploymentV3<'f, A>> for ExecutionV3<'f, A> {
276 fn from(value: &DeploymentV3<'f, A>) -> Self {
277 let mut calldata = vec![
278 value.factory.class_hash,
279 value.salt,
280 if value.unique { Felt::ONE } else { Felt::ZERO },
281 value.constructor_calldata.len().into(),
282 ];
283 calldata.extend_from_slice(&value.constructor_calldata);
284
285 let execution = Self::new(
286 vec![Call {
287 to: value.factory.udc_address,
288 selector: SELECTOR_DEPLOYCONTRACT,
289 calldata,
290 }],
291 &value.factory.account,
292 );
293
294 let execution = if let Some(nonce) = value.nonce {
295 execution.nonce(nonce)
296 } else {
297 execution
298 };
299
300 let execution = if let Some(l1_gas) = value.l1_gas {
301 execution.l1_gas(l1_gas)
302 } else {
303 execution
304 };
305
306 let execution = if let Some(l1_gas_price) = value.l1_gas_price {
307 execution.l1_gas_price(l1_gas_price)
308 } else {
309 execution
310 };
311
312 let execution = if let Some(l2_gas) = value.l2_gas {
313 execution.l2_gas(l2_gas)
314 } else {
315 execution
316 };
317
318 let execution = if let Some(l2_gas_price) = value.l2_gas_price {
319 execution.l2_gas_price(l2_gas_price)
320 } else {
321 execution
322 };
323
324 let execution = if let Some(l1_data_gas) = value.l1_data_gas {
325 execution.l1_data_gas(l1_data_gas)
326 } else {
327 execution
328 };
329
330 let execution = if let Some(l1_data_gas_price) = value.l1_data_gas_price {
331 execution.l1_data_gas_price(l1_data_gas_price)
332 } else {
333 execution
334 };
335
336 let execution = if let Some(tip) = value.tip {
337 execution.tip(tip)
338 } else {
339 execution
340 };
341
342 let execution = execution.gas_estimate_multiplier(value.gas_estimate_multiplier);
343
344 execution.gas_price_estimate_multiplier(value.gas_price_estimate_multiplier)
345 }
346}
347
348impl UdcSelector {
349 pub const fn address(&self) -> Felt {
351 match self {
352 Self::Legacy => LEGACY_UDC_ADDRESS,
353 Self::New => NEW_UDC_ADDRESS,
354 Self::Custom(address) => *address,
355 }
356 }
357}
358
359impl From<UdcSelector> for Felt {
360 fn from(value: UdcSelector) -> Self {
361 value.address()
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use starknet_rust_accounts::{ExecutionEncoding, SingleOwnerAccount};
368 use starknet_rust_core::chain_id;
369 use starknet_rust_providers::SequencerGatewayProvider;
370 use starknet_rust_signers::{LocalWallet, SigningKey};
371
372 use super::*;
373
374 #[allow(deprecated)]
375 #[test]
376 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
377 fn test_deployed_address_unique() {
378 let factory = ContractFactory::new(
379 Felt::from_hex("0x2bfd9564754d9b4a326da62b2f22b8fea7bbeffd62da4fcaea986c323b7aeb")
380 .unwrap(),
381 SingleOwnerAccount::new(
382 SequencerGatewayProvider::starknet_alpha_sepolia(),
383 LocalWallet::from_signing_key(SigningKey::from_random()),
384 Felt::from_hex("0xb1461de04c6a1aa3375bdf9b7723a8779c082ffe21311d683a0b15c078b5dc")
385 .unwrap(),
386 chain_id::SEPOLIA,
387 ExecutionEncoding::Legacy,
388 ),
389 );
390
391 let unique_address_v3 = factory
392 .deploy_v3(
393 vec![Felt::from_hex("0x1234").unwrap()],
394 Felt::from_hex("0x3456").unwrap(),
395 true,
396 )
397 .deployed_address();
398
399 let not_unique_address_v3 = factory
400 .deploy_v3(
401 vec![Felt::from_hex("0x1234").unwrap()],
402 Felt::from_hex("0x3456").unwrap(),
403 false,
404 )
405 .deployed_address();
406
407 assert_eq!(
408 unique_address_v3,
409 Felt::from_hex("0x36e05bcd41191387bc2f04ed9cad4776a75df3b748b0246a5d217a988474181")
410 .unwrap()
411 );
412
413 assert_eq!(
414 not_unique_address_v3,
415 Felt::from_hex("0x3a320b6aa0b451b22fba90b5d75b943932649137c09a86a5cf4853031be70c1")
416 .unwrap()
417 );
418 }
419}