1use serde::{de::Visitor, Deserialize, Serialize};
2use serde_with::serde_as;
3use starknet_rust_core::{
4 serde::unsigned_field_element::{UfeHex, UfePendingBlockHash},
5 types::Felt,
6};
7
8use super::{
9 serde_impls::{u128_hex, u64_hex, u64_hex_opt},
10 transaction_receipt::{TransactionExecutionStatus, TransactionFinalityStatus},
11 TransactionStatus,
12};
13
14#[derive(Debug, Clone, Deserialize)]
15#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
16#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
17pub enum TransactionType {
18 Declare(DeclareTransaction),
19 Deploy(DeployTransaction),
20 DeployAccount(DeployAccountTransaction),
21 InvokeFunction(InvokeFunctionTransaction),
22 L1Handler(L1HandlerTransaction),
23}
24
25#[serde_as]
26#[derive(Debug, Deserialize)]
27#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
28pub struct TransactionStatusInfo {
29 #[serde(default)]
30 #[serde_as(as = "UfePendingBlockHash")]
31 pub block_hash: Option<Felt>,
32 #[serde(alias = "tx_status")]
33 pub status: TransactionStatus,
34 #[serde(default)]
37 pub finality_status: Option<TransactionFinalityStatus>,
38 #[serde(default)]
39 #[serde(alias = "tx_revert_reason")]
40 pub transaction_revert_reason: Option<String>,
41 #[serde(default)]
42 pub execution_status: Option<TransactionExecutionStatus>,
43 #[serde(alias = "tx_failure_reason")]
44 pub transaction_failure_reason: Option<TransactionFailureReason>,
45}
46#[derive(Debug, Deserialize)]
47#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
48pub struct TransactionFailureReason {
49 pub code: String,
50 pub error_message: Option<String>,
51}
52
53#[serde_as]
54#[derive(Debug, Deserialize)]
55#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
56pub struct TransactionInfo {
57 #[serde(default)]
58 #[serde_as(as = "UfePendingBlockHash")]
59 pub block_hash: Option<Felt>,
60 pub block_number: Option<u64>,
61 pub status: TransactionStatus,
62 #[serde(default)]
65 pub finality_status: Option<TransactionFinalityStatus>,
66 #[serde(default)]
67 pub revert_error: Option<String>,
68 #[serde(default)]
69 pub execution_status: Option<TransactionExecutionStatus>,
70 #[serde(rename(deserialize = "transaction"))]
71 pub r#type: Option<TransactionType>,
72 pub transaction_failure_reason: Option<TransactionFailureReason>,
73 pub transaction_index: Option<u64>,
74}
75
76#[derive(Debug, Deserialize, PartialEq, Eq)]
77#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
78#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
79pub enum EntryPointType {
80 External,
81 L1Handler,
82 Constructor,
83}
84
85#[serde_as]
86#[derive(Debug, Clone, Deserialize)]
87#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
88pub struct DeclareTransaction {
89 #[serde_as(as = "UfeHex")]
90 pub class_hash: Felt,
91 #[serde_as(as = "Option<UfeHex>")]
92 pub compiled_class_hash: Option<Felt>,
93 #[serde_as(as = "UfeHex")]
94 pub sender_address: Felt,
95 #[serde_as(as = "UfeHex")]
96 pub nonce: Felt,
97 #[serde(default)]
98 #[serde_as(as = "Option<UfeHex>")]
99 pub max_fee: Option<Felt>,
100 #[serde_as(as = "UfeHex")]
101 pub version: Felt,
102 #[serde_as(as = "UfeHex")]
103 pub transaction_hash: Felt,
104 #[serde_as(deserialize_as = "Vec<UfeHex>")]
105 pub signature: Vec<Felt>,
106 pub nonce_data_availability_mode: Option<DataAvailabilityMode>,
107 pub fee_data_availability_mode: Option<DataAvailabilityMode>,
108 pub resource_bounds: Option<ResourceBoundsMapping>,
109 #[serde(default, with = "u64_hex_opt")]
110 pub tip: Option<u64>,
111 #[serde_as(as = "Option<Vec<UfeHex>>")]
112 pub paymaster_data: Option<Vec<Felt>>,
113 #[serde_as(deserialize_as = "Option<Vec<UfeHex>>")]
114 pub account_deployment_data: Option<Vec<Felt>>,
115}
116
117#[serde_as]
118#[derive(Debug, Clone, Deserialize)]
119#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
120pub struct DeployTransaction {
121 #[serde_as(deserialize_as = "Vec<UfeHex>")]
122 pub constructor_calldata: Vec<Felt>,
123 #[serde_as(as = "UfeHex")]
124 pub contract_address: Felt,
125 #[serde_as(as = "UfeHex")]
126 pub contract_address_salt: Felt,
127 #[serde_as(as = "UfeHex")]
128 pub class_hash: Felt,
129 #[serde_as(as = "UfeHex")]
130 pub transaction_hash: Felt,
131 #[serde_as(as = "UfeHex")]
132 pub version: Felt,
133}
134
135#[serde_as]
136#[derive(Debug, Clone, Deserialize)]
137#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
138pub struct DeployAccountTransaction {
139 #[serde_as(deserialize_as = "Vec<UfeHex>")]
140 pub constructor_calldata: Vec<Felt>,
141 #[serde(default)]
142 #[serde_as(as = "Option<UfeHex>")]
143 pub contract_address: Option<Felt>,
144 #[serde_as(as = "UfeHex")]
145 pub contract_address_salt: Felt,
146 #[serde_as(as = "UfeHex")]
147 pub class_hash: Felt,
148 #[serde_as(as = "UfeHex")]
149 pub transaction_hash: Felt,
150 #[serde_as(as = "UfeHex")]
151 pub nonce: Felt,
152 #[serde_as(as = "UfeHex")]
153 pub version: Felt,
154 #[serde_as(deserialize_as = "Vec<UfeHex>")]
155 pub signature: Vec<Felt>,
156 #[serde(default)]
157 #[serde_as(as = "Option<UfeHex>")]
158 pub max_fee: Option<Felt>,
159 pub nonce_data_availability_mode: Option<DataAvailabilityMode>,
160 pub fee_data_availability_mode: Option<DataAvailabilityMode>,
161 pub resource_bounds: Option<ResourceBoundsMapping>,
162 #[serde(default, with = "u64_hex_opt")]
163 pub tip: Option<u64>,
164 #[serde_as(as = "Option<Vec<UfeHex>>")]
165 pub paymaster_data: Option<Vec<Felt>>,
166 #[serde(default)]
167 #[serde_as(as = "Option<UfeHex>")]
168 pub sender_address: Option<Felt>,
169}
170
171#[serde_as]
172#[derive(Debug, Clone, Deserialize)]
173#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
174pub struct InvokeFunctionTransaction {
175 #[serde_as(as = "UfeHex")]
176 #[serde(alias = "contract_address")]
178 pub sender_address: Felt,
179 #[serde_as(as = "Option<UfeHex>")]
180 pub entry_point_selector: Option<Felt>,
181 #[serde_as(deserialize_as = "Vec<UfeHex>")]
182 pub calldata: Vec<Felt>,
183 #[serde_as(deserialize_as = "Vec<UfeHex>")]
184 pub signature: Vec<Felt>,
185 #[serde_as(as = "UfeHex")]
186 pub transaction_hash: Felt,
187 #[serde(default)]
188 #[serde_as(as = "Option<UfeHex>")]
189 pub max_fee: Option<Felt>,
190 #[serde_as(as = "Option<UfeHex>")]
191 pub nonce: Option<Felt>,
192 pub nonce_data_availability_mode: Option<DataAvailabilityMode>,
193 pub fee_data_availability_mode: Option<DataAvailabilityMode>,
194 pub resource_bounds: Option<ResourceBoundsMapping>,
195 #[serde(default, with = "u64_hex_opt")]
196 pub tip: Option<u64>,
197 #[serde_as(as = "Option<Vec<UfeHex>>")]
198 pub paymaster_data: Option<Vec<Felt>>,
199 #[serde_as(deserialize_as = "Option<Vec<UfeHex>>")]
200 pub account_deployment_data: Option<Vec<Felt>>,
201 #[serde_as(as = "UfeHex")]
202 pub version: Felt,
203}
204
205#[serde_as]
206#[derive(Debug, Clone, Deserialize)]
207#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
208pub struct L1HandlerTransaction {
209 #[serde_as(as = "UfeHex")]
210 pub contract_address: Felt,
211 #[serde_as(as = "UfeHex")]
212 pub entry_point_selector: Felt,
213 #[serde_as(deserialize_as = "Vec<UfeHex>")]
214 pub calldata: Vec<Felt>,
215 #[serde_as(as = "UfeHex")]
216 pub transaction_hash: Felt,
217 #[serde_as(as = "Option<UfeHex>")]
218 pub nonce: Option<Felt>,
219 #[serde_as(as = "UfeHex")]
220 pub version: Felt,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
224#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
225#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
226pub struct ResourceBoundsMapping {
227 #[serde(default)]
228 pub l1_data_gas: ResourceBounds,
229 pub l1_gas: ResourceBounds,
230 pub l2_gas: ResourceBounds,
231}
232
233#[derive(Debug, Default, Clone, Serialize, Deserialize)]
234#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
235pub struct ResourceBounds {
236 #[serde(with = "u64_hex")]
237 pub max_amount: u64,
238 #[serde(with = "u128_hex")]
239 pub max_price_per_unit: u128,
240}
241
242#[derive(Debug, Clone, Copy)]
243pub enum DataAvailabilityMode {
244 L1,
245 L2,
246}
247
248struct DataAvailabilityModeVisitor;
249
250impl TransactionType {
251 pub const fn transaction_hash(&self) -> Felt {
252 match self {
253 Self::Declare(inner) => inner.transaction_hash,
254 Self::Deploy(inner) => inner.transaction_hash,
255 Self::DeployAccount(inner) => inner.transaction_hash,
256 Self::InvokeFunction(inner) => inner.transaction_hash,
257 Self::L1Handler(inner) => inner.transaction_hash,
258 }
259 }
260}
261
262impl Serialize for DataAvailabilityMode {
263 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
264 where
265 S: serde::Serializer,
266 {
267 serializer.serialize_u32(match self {
268 Self::L1 => 0,
269 Self::L2 => 1,
270 })
271 }
272}
273
274impl<'de> Deserialize<'de> for DataAvailabilityMode {
275 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
276 where
277 D: serde::Deserializer<'de>,
278 {
279 deserializer.deserialize_any(DataAvailabilityModeVisitor)
280 }
281}
282
283impl Visitor<'_> for DataAvailabilityModeVisitor {
284 type Value = DataAvailabilityMode;
285
286 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287 write!(formatter, "integer")
288 }
289
290 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
291 where
292 E: serde::de::Error,
293 {
294 match v {
295 0 => Ok(DataAvailabilityMode::L1),
296 1 => Ok(DataAvailabilityMode::L2),
297 _ => Err(serde::de::Error::invalid_value(
298 serde::de::Unexpected::Unsigned(v),
299 &"0 or 1",
300 )),
301 }
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308
309 #[test]
310 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
311 fn test_deser_full_invoke_transaction() {
312 let raw =
313 include_str!("../../../test-data/raw_gateway_responses/get_transaction/1_invoke.txt");
314 let tx: TransactionInfo = serde_json::from_str(raw).unwrap();
315
316 assert_eq!(tx.block_number, Some(5));
317 if let TransactionType::InvokeFunction(invoke) = tx.r#type.unwrap() {
318 assert_eq!(invoke.signature.len(), 2);
319 } else {
320 panic!("Did not deserialize TransactionType::InvokeFunction properly")
321 }
322 }
323
324 #[test]
325 #[ignore = "transaction with the same criteria not found in alpha-sepolia yet"]
326 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
327 fn test_deser_full_deploy_transaction() {
328 let raw =
329 include_str!("../../../test-data/raw_gateway_responses/get_transaction/2_deploy.txt");
330 let tx: TransactionInfo = serde_json::from_str(raw).unwrap();
331
332 assert_eq!(tx.block_number, Some(100));
333 if let TransactionType::Deploy(deploy) = tx.r#type.unwrap() {
334 assert_eq!(deploy.constructor_calldata.len(), 2);
335 } else {
336 panic!("Did not deserialize TransactionType::Deploy properly");
337 }
338 }
339
340 #[test]
341 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
342 fn test_deser_not_received() {
343 let raw = include_str!(
344 "../../../test-data/raw_gateway_responses/get_transaction/3_not_received.txt"
345 );
346 let tx: TransactionInfo = serde_json::from_str(raw).unwrap();
347
348 assert_eq!(tx.block_number, None);
349 assert!(tx.status.is_not_received());
350 }
351
352 #[test]
353 #[ignore = "transaction with the same criteria not found in alpha-sepolia yet"]
354 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
355 fn test_deser_failure() {
356 let raw =
357 include_str!("../../../test-data/raw_gateway_responses/get_transaction/4_failure.txt");
358 let tx: TransactionInfo = serde_json::from_str(raw).unwrap();
359
360 assert!(tx.transaction_failure_reason.is_some());
361 let failure_reason = tx.transaction_failure_reason.unwrap();
362 assert_eq!(failure_reason.code, "TRANSACTION_FAILED");
363 }
364
365 #[test]
366 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
367 fn test_deser_declare_v1_transaction() {
368 let raw = include_str!(
369 "../../../test-data/raw_gateway_responses/get_transaction/5_declare_v1.txt"
370 );
371 let tx: TransactionInfo = serde_json::from_str(raw).unwrap();
372
373 match tx.r#type.unwrap() {
374 TransactionType::Declare(_) => {}
375 _ => panic!("Did not deserialize TransactionType::Declare properly"),
376 }
377 }
378
379 #[test]
380 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
381 fn test_deser_declare_v2_transaction() {
382 let raw = include_str!(
383 "../../../test-data/raw_gateway_responses/get_transaction/6_declare_v2.txt"
384 );
385 let tx: TransactionInfo = serde_json::from_str(raw).unwrap();
386
387 match tx.r#type.unwrap() {
388 TransactionType::Declare(_) => {}
389 _ => panic!("Did not deserialize TransactionType::Declare properly"),
390 }
391 }
392
393 #[test]
394 #[ignore = "transaction with the same criteria not found in alpha-sepolia yet"]
395 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
396 fn test_deser_reverted() {
397 let raw =
398 include_str!("../../../test-data/raw_gateway_responses/get_transaction/7_reverted.txt");
399 let tx: TransactionInfo = serde_json::from_str(raw).unwrap();
400
401 match tx.execution_status.unwrap() {
402 TransactionExecutionStatus::Reverted => {}
403 _ => panic!("Unexpected execution status"),
404 }
405 }
406
407 #[test]
408 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
409 fn test_deser_invoke_v3_transaction() {
410 let raw = include_str!(
411 "../../../test-data/raw_gateway_responses/get_transaction/8_invoke_v3.txt"
412 );
413 let tx: TransactionInfo = serde_json::from_str(raw).unwrap();
414
415 match tx.r#type.unwrap() {
416 TransactionType::InvokeFunction(tx) => {
417 assert_eq!(tx.version, Felt::THREE);
418 }
419 _ => panic!("Did not deserialize TransactionType::InvokeFunction properly"),
420 }
421 }
422
423 #[test]
424 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
425 fn test_deser_declare_v3_transaction() {
426 let raw = include_str!(
427 "../../../test-data/raw_gateway_responses/get_transaction/9_declare_v3.txt"
428 );
429 let tx: TransactionInfo = serde_json::from_str(raw).unwrap();
430
431 match tx.r#type.unwrap() {
432 TransactionType::Declare(tx) => {
433 assert_eq!(tx.version, Felt::THREE);
434 }
435 _ => panic!("Did not deserialize TransactionType::Declare properly"),
436 }
437 }
438
439 #[test]
440 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
441 fn test_deser_deploy_account_v3_transaction() {
442 let raw = include_str!(
443 "../../../test-data/raw_gateway_responses/get_transaction/10_deploy_account_v3.txt"
444 );
445 let tx: TransactionInfo = serde_json::from_str(raw).unwrap();
446
447 match tx.r#type.unwrap() {
448 TransactionType::DeployAccount(tx) => {
449 assert_eq!(tx.version, Felt::THREE);
450 }
451 _ => panic!("Did not deserialize TransactionType::DeployAccount properly"),
452 }
453 }
454
455 #[test]
456 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
457 fn test_deser_brief_accepted() {
458 let raw = include_str!(
459 "../../../test-data/raw_gateway_responses/get_transaction_status/1_accepted.txt"
460 );
461
462 let tx: TransactionStatusInfo = serde_json::from_str(raw).unwrap();
463
464 assert!(tx.status.is_accepted_on_l1());
465 assert_eq!(
466 tx.block_hash,
467 Some(
468 Felt::from_hex("0x13b390a0b2c48f907cda28c73a12aa31b96d51bc1be004ba5f71174d8d70e4f")
469 .unwrap()
470 )
471 );
472 }
473
474 #[test]
475 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
476 fn test_deser_brief_not_received() {
477 let raw = include_str!(
478 "../../../test-data/raw_gateway_responses/get_transaction_status/2_not_received.txt"
479 );
480
481 let tx: TransactionStatusInfo = serde_json::from_str(raw).unwrap();
482
483 assert!(tx.status.is_not_received());
484 assert!(tx.block_hash.is_none());
485 }
486
487 #[test]
488 #[ignore = "transaction with the same criteria not found in alpha-sepolia yet"]
489 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
490 fn test_deser_brief_failure() {
491 let raw = include_str!(
492 "../../../test-data/raw_gateway_responses/get_transaction_status/3_failure.txt"
493 );
494
495 let tx: TransactionStatusInfo = serde_json::from_str(raw).unwrap();
496
497 assert!(tx.status.is_rejected());
498 assert!(tx.block_hash.is_none());
499 assert!(tx.transaction_failure_reason.is_some());
500 }
501
502 #[test]
503 #[ignore = "transaction with the same criteria not found in alpha-sepolia yet"]
504 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
505 fn test_deser_brief_reverted() {
506 let raw = include_str!(
507 "../../../test-data/raw_gateway_responses/get_transaction_status/4_reverted.txt"
508 );
509
510 let tx: TransactionStatusInfo = serde_json::from_str(raw).unwrap();
511
512 assert_eq!(tx.status, TransactionStatus::Reverted);
513 assert!(tx.block_hash.is_some());
514 assert!(tx.transaction_failure_reason.is_none());
515 assert!(tx.transaction_revert_reason.is_some());
516 }
517}