starknet_rust_providers/sequencer/models/
conversions.rs

1use std::sync::Arc;
2
3use starknet_rust_core::types::{self as core, contract::legacy as contract_legacy, Felt};
4
5use super::{
6    state_update::{DeclaredContract, DeployedContract, StateDiff, StorageDiff},
7    transaction::{DataAvailabilityMode, ResourceBounds, ResourceBoundsMapping},
8    *,
9};
10
11#[derive(Debug, thiserror::Error)]
12#[error("unable to convert type")]
13pub struct ConversionError;
14
15pub(crate) struct ConfirmedReceiptWithContext {
16    pub receipt: ConfirmedTransactionReceipt,
17    pub transaction: TransactionType,
18    pub finality: BlockStatus,
19}
20
21impl TryFrom<core::BlockId> for BlockId {
22    type Error = ConversionError;
23
24    fn try_from(value: core::BlockId) -> Result<Self, Self::Error> {
25        match value {
26            core::BlockId::Hash(hash) => Ok(Self::Hash(hash)),
27            core::BlockId::Number(num) => Ok(Self::Number(num)),
28            core::BlockId::Tag(core::BlockTag::Latest) => Ok(Self::Latest),
29            core::BlockId::Tag(core::BlockTag::PreConfirmed) => Ok(Self::Pending),
30            core::BlockId::Tag(core::BlockTag::L1Accepted) => Err(ConversionError),
31        }
32    }
33}
34
35impl TryFrom<Block> for core::MaybePreConfirmedBlockWithTxHashes {
36    type Error = ConversionError;
37
38    fn try_from(value: Block) -> Result<Self, Self::Error> {
39        match (value.block_hash, value.block_number, value.state_root) {
40            // Confirmed block
41            (Some(block_hash), Some(block_number), Some(state_root)) => {
42                Ok(Self::Block(core::BlockWithTxHashes {
43                    status: value.status.try_into()?,
44                    block_hash,
45                    parent_hash: value.parent_block_hash,
46                    block_number,
47                    new_root: state_root,
48                    timestamp: value.timestamp,
49                    sequencer_address: value.sequencer_address.unwrap_or_default(),
50                    l1_gas_price: value.l1_gas_price,
51                    l2_gas_price: value.l2_gas_price,
52                    l1_data_gas_price: value.l1_data_gas_price,
53                    l1_da_mode: value.l1_da_mode,
54                    starknet_version: value.starknet_version.ok_or(ConversionError)?,
55                    transactions: value
56                        .transactions
57                        .iter()
58                        .map(|tx| tx.transaction_hash())
59                        .collect(),
60                }))
61            }
62            // Pending block
63            (None, None, None) => {
64                // We're almost able to map this into a pre-confirmed block, but can't do it as
65                // pending blocks from the sequencer doesn't contain block number.
66                //
67                // Technically the block number can be found by looking up the parent block, but we
68                // don't have a choice but to error here.
69                Err(ConversionError)
70            }
71            // Unknown combination
72            _ => Err(ConversionError),
73        }
74    }
75}
76
77impl TryFrom<Block> for core::MaybePreConfirmedBlockWithTxs {
78    type Error = ConversionError;
79
80    fn try_from(value: Block) -> Result<Self, Self::Error> {
81        match (value.block_hash, value.block_number, value.state_root) {
82            // Confirmed block
83            (Some(block_hash), Some(block_number), Some(state_root)) => {
84                Ok(Self::Block(core::BlockWithTxs {
85                    status: value.status.try_into()?,
86                    block_hash,
87                    parent_hash: value.parent_block_hash,
88                    block_number,
89                    new_root: state_root,
90                    timestamp: value.timestamp,
91                    sequencer_address: value.sequencer_address.unwrap_or_default(),
92                    l1_gas_price: value.l1_gas_price,
93                    l2_gas_price: value.l2_gas_price,
94                    l1_data_gas_price: value.l1_data_gas_price,
95                    l1_da_mode: value.l1_da_mode,
96                    starknet_version: value.starknet_version.ok_or(ConversionError)?,
97                    transactions: value
98                        .transactions
99                        .into_iter()
100                        .map(|tx| tx.try_into())
101                        .collect::<Result<_, _>>()?,
102                }))
103            }
104            // Pending block
105            (None, None, None) => {
106                // We're almost able to map this into a pre-confirmed block, but can't do it as
107                // pending blocks from the sequencer doesn't contain block number.
108                //
109                // Technically the block number can be found by looking up the parent block, but we
110                // don't have a choice but to error here.
111                Err(ConversionError)
112            }
113            // Unknown combination
114            _ => Err(ConversionError),
115        }
116    }
117}
118
119impl TryFrom<Block> for core::MaybePreConfirmedBlockWithReceipts {
120    type Error = ConversionError;
121
122    fn try_from(value: Block) -> Result<Self, Self::Error> {
123        if value.transactions.len() != value.transaction_receipts.len() {
124            return Err(ConversionError);
125        }
126
127        let mut transactions = vec![];
128
129        for (tx, receipt) in value
130            .transactions
131            .into_iter()
132            .zip(value.transaction_receipts.into_iter())
133        {
134            let core_tx = tx.clone().try_into()?;
135
136            let tx_with_receipt = ConfirmedReceiptWithContext {
137                receipt,
138                transaction: tx,
139                finality: value.status,
140            };
141
142            transactions.push(core::TransactionWithReceipt {
143                transaction: core_tx,
144                receipt: tx_with_receipt.try_into()?,
145            });
146        }
147
148        match (value.block_hash, value.block_number, value.state_root) {
149            // Confirmed block
150            (Some(block_hash), Some(block_number), Some(state_root)) => {
151                Ok(Self::Block(core::BlockWithReceipts {
152                    status: value.status.try_into()?,
153                    block_hash,
154                    parent_hash: value.parent_block_hash,
155                    block_number,
156                    new_root: state_root,
157                    timestamp: value.timestamp,
158                    sequencer_address: value.sequencer_address.unwrap_or_default(),
159                    l1_gas_price: value.l1_gas_price,
160                    l2_gas_price: value.l2_gas_price,
161                    l1_data_gas_price: value.l1_data_gas_price,
162                    l1_da_mode: value.l1_da_mode,
163                    starknet_version: value.starknet_version.ok_or(ConversionError)?,
164                    transactions,
165                }))
166            }
167            // Pending block
168            (None, None, None) => {
169                // We're almost able to map this into a pre-confirmed block, but can't do it as
170                // pending blocks from the sequencer doesn't contain block number.
171                //
172                // Technically the block number can be found by looking up the parent block, but we
173                // don't have a choice but to error here.
174                Err(ConversionError)
175            }
176            // Unknown combination
177            _ => Err(ConversionError),
178        }
179    }
180}
181
182impl TryFrom<BlockStatus> for core::BlockStatus {
183    type Error = ConversionError;
184
185    fn try_from(value: BlockStatus) -> Result<Self, Self::Error> {
186        match value {
187            BlockStatus::Pending => Ok(Self::PreConfirmed),
188            BlockStatus::Aborted | BlockStatus::Reverted => Err(ConversionError),
189            BlockStatus::AcceptedOnL2 => Ok(Self::AcceptedOnL2),
190            BlockStatus::AcceptedOnL1 => Ok(Self::AcceptedOnL1),
191        }
192    }
193}
194
195impl TryFrom<TransactionType> for core::Transaction {
196    type Error = ConversionError;
197
198    fn try_from(value: TransactionType) -> Result<Self, Self::Error> {
199        match value {
200            TransactionType::Declare(inner) => Ok(Self::Declare(inner.try_into()?)),
201            TransactionType::Deploy(inner) => Ok(Self::Deploy(inner.try_into()?)),
202            TransactionType::DeployAccount(inner) => Ok(Self::DeployAccount(inner.try_into()?)),
203            TransactionType::InvokeFunction(inner) => Ok(Self::Invoke(inner.try_into()?)),
204            TransactionType::L1Handler(inner) => Ok(Self::L1Handler(inner.try_into()?)),
205        }
206    }
207}
208
209impl TryFrom<TransactionType> for core::TransactionContent {
210    type Error = ConversionError;
211
212    fn try_from(value: TransactionType) -> Result<Self, Self::Error> {
213        let tx: core::Transaction = value.try_into()?;
214        Ok(tx.into())
215    }
216}
217
218impl TryFrom<DeclareTransaction> for core::DeclareTransaction {
219    type Error = ConversionError;
220
221    fn try_from(value: DeclareTransaction) -> Result<Self, Self::Error> {
222        if value.version == Felt::ZERO {
223            Ok(Self::V0(core::DeclareTransactionV0 {
224                transaction_hash: value.transaction_hash,
225                max_fee: value.max_fee.ok_or(ConversionError)?,
226                signature: value.signature,
227                class_hash: value.class_hash,
228                sender_address: value.sender_address,
229            }))
230        } else if value.version == Felt::ONE {
231            Ok(Self::V1(core::DeclareTransactionV1 {
232                transaction_hash: value.transaction_hash,
233                max_fee: value.max_fee.ok_or(ConversionError)?,
234                signature: value.signature,
235                nonce: value.nonce,
236                class_hash: value.class_hash,
237                sender_address: value.sender_address,
238            }))
239        } else if value.version == Felt::TWO {
240            Ok(Self::V2(core::DeclareTransactionV2 {
241                transaction_hash: value.transaction_hash,
242                max_fee: value.max_fee.ok_or(ConversionError)?,
243                signature: value.signature,
244                nonce: value.nonce,
245                class_hash: value.class_hash,
246                compiled_class_hash: value.compiled_class_hash.ok_or(ConversionError)?,
247                sender_address: value.sender_address,
248            }))
249        } else if value.version == Felt::THREE {
250            Ok(Self::V3(core::DeclareTransactionV3 {
251                transaction_hash: value.transaction_hash,
252                sender_address: value.sender_address,
253                compiled_class_hash: value.compiled_class_hash.ok_or(ConversionError)?,
254                signature: value.signature,
255                nonce: value.nonce,
256                class_hash: value.class_hash,
257                resource_bounds: value.resource_bounds.ok_or(ConversionError)?.into(),
258                tip: value.tip.ok_or(ConversionError)?,
259                paymaster_data: value.paymaster_data.ok_or(ConversionError)?,
260                account_deployment_data: value.account_deployment_data.ok_or(ConversionError)?,
261                nonce_data_availability_mode: value
262                    .nonce_data_availability_mode
263                    .ok_or(ConversionError)?
264                    .into(),
265                fee_data_availability_mode: value
266                    .fee_data_availability_mode
267                    .ok_or(ConversionError)?
268                    .into(),
269            }))
270        } else {
271            Err(ConversionError)
272        }
273    }
274}
275
276impl TryFrom<DeployTransaction> for core::DeployTransaction {
277    type Error = ConversionError;
278
279    fn try_from(value: DeployTransaction) -> Result<Self, Self::Error> {
280        Ok(Self {
281            transaction_hash: value.transaction_hash,
282            class_hash: value.class_hash,
283            version: value.version,
284            contract_address_salt: value.contract_address_salt,
285            constructor_calldata: value.constructor_calldata,
286        })
287    }
288}
289
290impl TryFrom<DeployAccountTransaction> for core::DeployAccountTransaction {
291    type Error = ConversionError;
292
293    fn try_from(value: DeployAccountTransaction) -> Result<Self, Self::Error> {
294        if value.version == Felt::ONE {
295            Ok(Self::V1(core::DeployAccountTransactionV1 {
296                transaction_hash: value.transaction_hash,
297                max_fee: value.max_fee.ok_or(ConversionError)?,
298                signature: value.signature,
299                nonce: value.nonce,
300                contract_address_salt: value.contract_address_salt,
301                constructor_calldata: value.constructor_calldata,
302                class_hash: value.class_hash,
303            }))
304        } else if value.version == Felt::THREE {
305            Ok(Self::V3(core::DeployAccountTransactionV3 {
306                transaction_hash: value.transaction_hash,
307                signature: value.signature,
308                nonce: value.nonce,
309                contract_address_salt: value.contract_address_salt,
310                constructor_calldata: value.constructor_calldata,
311                class_hash: value.class_hash,
312                resource_bounds: value.resource_bounds.ok_or(ConversionError)?.into(),
313                tip: value.tip.ok_or(ConversionError)?,
314                paymaster_data: value.paymaster_data.ok_or(ConversionError)?,
315                nonce_data_availability_mode: value
316                    .nonce_data_availability_mode
317                    .ok_or(ConversionError)?
318                    .into(),
319                fee_data_availability_mode: value
320                    .fee_data_availability_mode
321                    .ok_or(ConversionError)?
322                    .into(),
323            }))
324        } else {
325            Err(ConversionError)
326        }
327    }
328}
329
330impl TryFrom<InvokeFunctionTransaction> for core::InvokeTransaction {
331    type Error = ConversionError;
332
333    fn try_from(value: InvokeFunctionTransaction) -> Result<Self, Self::Error> {
334        if value.version == Felt::ZERO {
335            Ok(Self::V0(core::InvokeTransactionV0 {
336                transaction_hash: value.transaction_hash,
337                max_fee: value.max_fee.ok_or(ConversionError)?,
338                signature: value.signature,
339                contract_address: value.sender_address,
340                entry_point_selector: value.entry_point_selector.ok_or(ConversionError)?,
341                calldata: value.calldata,
342            }))
343        } else if value.version == Felt::ONE {
344            Ok(Self::V1(core::InvokeTransactionV1 {
345                transaction_hash: value.transaction_hash,
346                max_fee: value.max_fee.ok_or(ConversionError)?,
347                signature: value.signature,
348                nonce: value.nonce.ok_or(ConversionError)?,
349                sender_address: value.sender_address,
350                calldata: value.calldata,
351            }))
352        } else if value.version == Felt::THREE {
353            Ok(Self::V3(core::InvokeTransactionV3 {
354                transaction_hash: value.transaction_hash,
355                sender_address: value.sender_address,
356                calldata: value.calldata,
357                signature: value.signature,
358                nonce: value.nonce.ok_or(ConversionError)?,
359                resource_bounds: value.resource_bounds.ok_or(ConversionError)?.into(),
360                tip: value.tip.ok_or(ConversionError)?,
361                paymaster_data: value.paymaster_data.ok_or(ConversionError)?,
362                account_deployment_data: value.account_deployment_data.ok_or(ConversionError)?,
363                nonce_data_availability_mode: value
364                    .nonce_data_availability_mode
365                    .ok_or(ConversionError)?
366                    .into(),
367                fee_data_availability_mode: value
368                    .fee_data_availability_mode
369                    .ok_or(ConversionError)?
370                    .into(),
371            }))
372        } else {
373            Err(ConversionError)
374        }
375    }
376}
377
378impl TryFrom<L1HandlerTransaction> for core::L1HandlerTransaction {
379    type Error = ConversionError;
380
381    fn try_from(value: L1HandlerTransaction) -> Result<Self, Self::Error> {
382        Ok(Self {
383            transaction_hash: value.transaction_hash,
384            version: value.version,
385            nonce: {
386                // TODO: remove this when a proper u64 conversion is implemented for `Felt`
387                let nonce_bytes = value.nonce.unwrap_or_default().to_bytes_le();
388                if nonce_bytes.iter().skip(8).any(|&x| x != 0) {
389                    return Err(ConversionError);
390                }
391                u64::from_le_bytes(nonce_bytes[..8].try_into().unwrap())
392            },
393            contract_address: value.contract_address,
394            entry_point_selector: value.entry_point_selector,
395            calldata: value.calldata,
396        })
397    }
398}
399
400impl From<ResourceBoundsMapping> for core::ResourceBoundsMapping {
401    fn from(value: ResourceBoundsMapping) -> Self {
402        Self {
403            l1_gas: value.l1_gas.into(),
404            l1_data_gas: value.l1_data_gas.into(),
405            l2_gas: value.l2_gas.into(),
406        }
407    }
408}
409
410impl From<core::ResourceBoundsMapping> for ResourceBoundsMapping {
411    fn from(value: core::ResourceBoundsMapping) -> Self {
412        Self {
413            l1_gas: value.l1_gas.into(),
414            l1_data_gas: value.l1_data_gas.into(),
415            l2_gas: value.l2_gas.into(),
416        }
417    }
418}
419
420impl From<ResourceBounds> for core::ResourceBounds {
421    fn from(value: ResourceBounds) -> Self {
422        Self {
423            max_amount: value.max_amount,
424            max_price_per_unit: value.max_price_per_unit,
425        }
426    }
427}
428
429impl From<core::ResourceBounds> for ResourceBounds {
430    fn from(value: core::ResourceBounds) -> Self {
431        Self {
432            max_amount: value.max_amount,
433            max_price_per_unit: value.max_price_per_unit,
434        }
435    }
436}
437
438impl From<DataAvailabilityMode> for core::DataAvailabilityMode {
439    fn from(value: DataAvailabilityMode) -> Self {
440        match value {
441            DataAvailabilityMode::L1 => Self::L1,
442            DataAvailabilityMode::L2 => Self::L2,
443        }
444    }
445}
446
447impl From<core::DataAvailabilityMode> for DataAvailabilityMode {
448    fn from(value: core::DataAvailabilityMode) -> Self {
449        match value {
450            core::DataAvailabilityMode::L1 => Self::L1,
451            core::DataAvailabilityMode::L2 => Self::L2,
452        }
453    }
454}
455
456impl TryFrom<StateUpdate> for core::MaybePreConfirmedStateUpdate {
457    type Error = ConversionError;
458
459    fn try_from(value: StateUpdate) -> Result<Self, Self::Error> {
460        match (value.block_hash, value.new_root, value.old_root) {
461            (Some(block_hash), Some(new_root), Some(old_root)) => {
462                Ok(Self::Update(core::StateUpdate {
463                    block_hash,
464                    new_root,
465                    old_root,
466                    state_diff: value.state_diff.into(),
467                }))
468            }
469            (None, None, old_root) => Ok(Self::PreConfirmedUpdate(core::PreConfirmedStateUpdate {
470                old_root,
471                state_diff: value.state_diff.into(),
472            })),
473            _ => Err(ConversionError),
474        }
475    }
476}
477
478impl From<StateDiff> for core::StateDiff {
479    fn from(value: StateDiff) -> Self {
480        Self {
481            storage_diffs: value
482                .storage_diffs
483                .into_iter()
484                .map(|(key, value)| core::ContractStorageDiffItem {
485                    address: key,
486                    storage_entries: value.into_iter().map(|item| item.into()).collect(),
487                })
488                .collect(),
489            deprecated_declared_classes: value.old_declared_contracts,
490            declared_classes: value
491                .declared_classes
492                .into_iter()
493                .map(|item| item.into())
494                .collect(),
495            migrated_compiled_classes: value
496                .migrated_compiled_classes
497                .map(|classes| classes.into_iter().map(|item| item.into()).collect()),
498            deployed_contracts: value
499                .deployed_contracts
500                .into_iter()
501                .map(|item| item.into())
502                .collect(),
503            replaced_classes: value
504                .replaced_classes
505                .into_iter()
506                .map(|item| item.into())
507                .collect(),
508            nonces: value
509                .nonces
510                .into_iter()
511                .map(|(key, value)| core::NonceUpdate {
512                    contract_address: key,
513                    nonce: value,
514                })
515                .collect(),
516        }
517    }
518}
519
520impl From<StorageDiff> for core::StorageEntry {
521    fn from(value: StorageDiff) -> Self {
522        Self {
523            key: value.key,
524            value: value.value,
525        }
526    }
527}
528
529impl From<DeclaredContract> for core::DeclaredClassItem {
530    fn from(value: DeclaredContract) -> Self {
531        Self {
532            class_hash: value.class_hash,
533            compiled_class_hash: value.compiled_class_hash,
534        }
535    }
536}
537
538impl From<DeclaredContract> for core::MigratedCompiledClassItem {
539    fn from(value: DeclaredContract) -> Self {
540        Self {
541            class_hash: value.class_hash,
542            compiled_class_hash: value.compiled_class_hash,
543        }
544    }
545}
546
547impl From<DeployedContract> for core::DeployedContractItem {
548    fn from(value: DeployedContract) -> Self {
549        Self {
550            address: value.address,
551            class_hash: value.class_hash,
552        }
553    }
554}
555
556impl From<DeployedContract> for core::ReplacedClassItem {
557    fn from(value: DeployedContract) -> Self {
558        Self {
559            contract_address: value.address,
560            class_hash: value.class_hash,
561        }
562    }
563}
564
565impl TryFrom<TransactionInfo> for core::Transaction {
566    type Error = ConversionError;
567
568    fn try_from(value: TransactionInfo) -> Result<Self, Self::Error> {
569        match value.r#type {
570            Some(tx) => tx.try_into(),
571            None => Err(ConversionError),
572        }
573    }
574}
575
576impl From<L2ToL1Message> for core::MsgToL1 {
577    fn from(value: L2ToL1Message) -> Self {
578        Self {
579            from_address: value.from_address,
580            // Unwrapping here is safe
581            to_address: Felt::from_bytes_be_slice(&value.to_address.0),
582            payload: value.payload,
583        }
584    }
585}
586
587impl From<Event> for core::Event {
588    fn from(value: Event) -> Self {
589        Self {
590            from_address: value.from_address,
591            keys: value.keys,
592            data: value.data,
593        }
594    }
595}
596
597impl TryFrom<TransactionExecutionStatus> for core::TransactionExecutionStatus {
598    type Error = ConversionError;
599
600    fn try_from(value: TransactionExecutionStatus) -> Result<Self, Self::Error> {
601        match value {
602            TransactionExecutionStatus::Succeeded => Ok(Self::Succeeded),
603            TransactionExecutionStatus::Reverted => Ok(Self::Reverted),
604            TransactionExecutionStatus::Rejected => Err(ConversionError),
605        }
606    }
607}
608
609impl TryFrom<TransactionFinalityStatus> for core::TransactionFinalityStatus {
610    type Error = ConversionError;
611
612    fn try_from(value: TransactionFinalityStatus) -> Result<Self, Self::Error> {
613        match value {
614            TransactionFinalityStatus::NotReceived | TransactionFinalityStatus::Received => {
615                Err(ConversionError)
616            }
617            TransactionFinalityStatus::AcceptedOnL2 => Ok(Self::AcceptedOnL2),
618            TransactionFinalityStatus::AcceptedOnL1 => Ok(Self::AcceptedOnL1),
619        }
620    }
621}
622
623impl From<core::BroadcastedInvokeTransaction> for InvokeFunctionTransactionRequest {
624    fn from(value: core::BroadcastedInvokeTransaction) -> Self {
625        Self::V3(value.into())
626    }
627}
628
629impl From<core::BroadcastedInvokeTransactionV3> for InvokeFunctionV3TransactionRequest {
630    fn from(value: core::BroadcastedInvokeTransactionV3) -> Self {
631        Self {
632            sender_address: value.sender_address,
633            calldata: value.calldata,
634            signature: value.signature,
635            nonce: value.nonce,
636            nonce_data_availability_mode: value.nonce_data_availability_mode.into(),
637            fee_data_availability_mode: value.fee_data_availability_mode.into(),
638            resource_bounds: value.resource_bounds.into(),
639            tip: value.tip,
640            paymaster_data: value.paymaster_data,
641            account_deployment_data: value.account_deployment_data,
642            is_query: value.is_query,
643        }
644    }
645}
646
647impl TryFrom<core::BroadcastedDeclareTransaction> for DeclareTransactionRequest {
648    type Error = ConversionError;
649
650    fn try_from(value: core::BroadcastedDeclareTransaction) -> Result<Self, Self::Error> {
651        Ok(Self::V3(value.try_into()?))
652    }
653}
654
655impl TryFrom<core::BroadcastedDeclareTransactionV3> for DeclareV3TransactionRequest {
656    type Error = ConversionError;
657
658    fn try_from(value: core::BroadcastedDeclareTransactionV3) -> Result<Self, Self::Error> {
659        Ok(Self {
660            contract_class: Arc::new(
661                contract::CompressedSierraClass::from_flattened(&value.contract_class)
662                    .map_err(|_| ConversionError)?,
663            ),
664            compiled_class_hash: value.compiled_class_hash,
665            sender_address: value.sender_address,
666            signature: value.signature,
667            nonce: value.nonce,
668            nonce_data_availability_mode: value.nonce_data_availability_mode.into(),
669            fee_data_availability_mode: value.fee_data_availability_mode.into(),
670            resource_bounds: value.resource_bounds.into(),
671            tip: value.tip,
672            paymaster_data: value.paymaster_data,
673            account_deployment_data: value.account_deployment_data,
674            is_query: value.is_query,
675        })
676    }
677}
678
679impl From<core::BroadcastedDeployAccountTransaction> for DeployAccountTransactionRequest {
680    fn from(value: core::BroadcastedDeployAccountTransaction) -> Self {
681        Self::V3(value.into())
682    }
683}
684
685impl From<core::BroadcastedDeployAccountTransactionV3> for DeployAccountV3TransactionRequest {
686    fn from(value: core::BroadcastedDeployAccountTransactionV3) -> Self {
687        Self {
688            class_hash: value.class_hash,
689            contract_address_salt: value.contract_address_salt,
690            constructor_calldata: value.constructor_calldata,
691            signature: value.signature,
692            nonce: value.nonce,
693            nonce_data_availability_mode: value.nonce_data_availability_mode.into(),
694            fee_data_availability_mode: value.fee_data_availability_mode.into(),
695            resource_bounds: value.resource_bounds.into(),
696            tip: value.tip,
697            paymaster_data: value.paymaster_data,
698            is_query: value.is_query,
699        }
700    }
701}
702
703impl From<core::CompressedLegacyContractClass> for CompressedLegacyContractClass {
704    fn from(value: core::CompressedLegacyContractClass) -> Self {
705        Self {
706            program: value.program,
707            entry_points_by_type: contract_legacy::RawLegacyEntryPoints {
708                constructor: value
709                    .entry_points_by_type
710                    .constructor
711                    .into_iter()
712                    .map(convert_legacy_entry_point)
713                    .collect(),
714                external: value
715                    .entry_points_by_type
716                    .external
717                    .into_iter()
718                    .map(convert_legacy_entry_point)
719                    .collect(),
720                l1_handler: value
721                    .entry_points_by_type
722                    .l1_handler
723                    .into_iter()
724                    .map(convert_legacy_entry_point)
725                    .collect(),
726            },
727            abi: value
728                .abi
729                .map(|abi| abi.into_iter().map(|item| item.into()).collect()),
730        }
731    }
732}
733
734impl TryFrom<DeployedClass> for core::ContractClass {
735    type Error = ConversionError;
736
737    fn try_from(value: DeployedClass) -> Result<Self, Self::Error> {
738        match value {
739            DeployedClass::SierraClass(inner) => Ok(Self::Sierra(inner)),
740            DeployedClass::LegacyClass(inner) => {
741                Ok(Self::Legacy(inner.compress().map_err(|_| ConversionError)?))
742            }
743        }
744    }
745}
746
747impl From<EntryPointType> for core::EntryPointType {
748    fn from(value: EntryPointType) -> Self {
749        match value {
750            EntryPointType::External => Self::External,
751            EntryPointType::L1Handler => Self::L1Handler,
752            EntryPointType::Constructor => Self::Constructor,
753        }
754    }
755}
756
757impl TryFrom<TransactionStatusInfo> for core::TransactionStatus {
758    type Error = ConversionError;
759
760    fn try_from(value: TransactionStatusInfo) -> Result<Self, Self::Error> {
761        if value.status.is_rejected() {
762            // Since Starknet v0.14.0 it's no longer possible to express rejected transactions.
763            return Err(ConversionError);
764        }
765
766        let exec_status = match value.execution_status.ok_or(ConversionError)? {
767            TransactionExecutionStatus::Succeeded => {
768                Some(core::TransactionExecutionStatus::Succeeded)
769            }
770            TransactionExecutionStatus::Reverted => {
771                Some(core::TransactionExecutionStatus::Reverted)
772            }
773            TransactionExecutionStatus::Rejected => None,
774        };
775
776        match value.finality_status {
777            Some(TransactionFinalityStatus::Received) => Ok(Self::Received),
778            Some(TransactionFinalityStatus::AcceptedOnL2) => {
779                let exec = match (
780                    exec_status.ok_or(ConversionError)?,
781                    value.transaction_failure_reason,
782                ) {
783                    (core::TransactionExecutionStatus::Succeeded, None) => {
784                        Ok(core::ExecutionResult::Succeeded)
785                    }
786                    (core::TransactionExecutionStatus::Reverted, Some(reason)) => {
787                        Ok(core::ExecutionResult::Reverted {
788                            reason: reason.error_message.unwrap_or(reason.code),
789                        })
790                    }
791                    _ => Err(ConversionError),
792                };
793
794                Ok(Self::AcceptedOnL2(exec?))
795            }
796            Some(TransactionFinalityStatus::AcceptedOnL1) => {
797                let exec = match (
798                    exec_status.ok_or(ConversionError)?,
799                    value.transaction_failure_reason,
800                ) {
801                    (core::TransactionExecutionStatus::Succeeded, None) => {
802                        Ok(core::ExecutionResult::Succeeded)
803                    }
804                    (core::TransactionExecutionStatus::Reverted, Some(reason)) => {
805                        Ok(core::ExecutionResult::Reverted {
806                            reason: reason.error_message.unwrap_or(reason.code),
807                        })
808                    }
809                    _ => Err(ConversionError),
810                };
811
812                Ok(Self::AcceptedOnL1(exec?))
813            }
814            // `NotReceived` must be handled on the caller before converting
815            _ => Err(ConversionError),
816        }
817    }
818}
819
820impl TryFrom<ConfirmedReceiptWithContext> for core::TransactionReceipt {
821    type Error = ConversionError;
822
823    fn try_from(value: ConfirmedReceiptWithContext) -> Result<Self, Self::Error> {
824        match value.transaction {
825            TransactionType::Declare(_) => Ok(Self::Declare(value.try_into()?)),
826            TransactionType::Deploy(_) => Ok(Self::Deploy(value.try_into()?)),
827            TransactionType::DeployAccount(_) => Ok(Self::DeployAccount(value.try_into()?)),
828            TransactionType::InvokeFunction(_) => Ok(Self::Invoke(value.try_into()?)),
829            TransactionType::L1Handler(_) => Ok(Self::L1Handler(value.try_into()?)),
830        }
831    }
832}
833
834impl TryFrom<ConfirmedReceiptWithContext> for core::DeclareTransactionReceipt {
835    type Error = ConversionError;
836
837    fn try_from(value: ConfirmedReceiptWithContext) -> Result<Self, Self::Error> {
838        Ok(Self {
839            transaction_hash: value.receipt.transaction_hash,
840            actual_fee: core::FeePayment {
841                amount: value.receipt.actual_fee,
842                unit: core::PriceUnit::Wei,
843            },
844            finality_status: value.finality.try_into()?,
845            messages_sent: value
846                .receipt
847                .l2_to_l1_messages
848                .into_iter()
849                .map(|item| item.into())
850                .collect(),
851            events: value
852                .receipt
853                .events
854                .into_iter()
855                .map(|item| item.into())
856                .collect(),
857            execution_resources: value
858                .receipt
859                .execution_resources
860                .ok_or(ConversionError)?
861                .total_gas_consumed
862                .ok_or(ConversionError)?,
863            execution_result: convert_execution_result(
864                value.receipt.execution_status,
865                value.receipt.revert_error,
866            )?,
867        })
868    }
869}
870
871impl TryFrom<ConfirmedReceiptWithContext> for core::DeployTransactionReceipt {
872    type Error = ConversionError;
873
874    fn try_from(value: ConfirmedReceiptWithContext) -> Result<Self, Self::Error> {
875        Ok(Self {
876            transaction_hash: value.receipt.transaction_hash,
877            actual_fee: core::FeePayment {
878                amount: value.receipt.actual_fee,
879                unit: core::PriceUnit::Wei,
880            },
881            finality_status: value.finality.try_into()?,
882            messages_sent: value
883                .receipt
884                .l2_to_l1_messages
885                .into_iter()
886                .map(|item| item.into())
887                .collect(),
888            events: value
889                .receipt
890                .events
891                .into_iter()
892                .map(|item| item.into())
893                .collect(),
894            contract_address: match value.transaction {
895                TransactionType::Deploy(inner) => inner.contract_address,
896                _ => return Err(ConversionError),
897            },
898            execution_resources: value
899                .receipt
900                .execution_resources
901                .ok_or(ConversionError)?
902                .total_gas_consumed
903                .ok_or(ConversionError)?,
904            execution_result: convert_execution_result(
905                value.receipt.execution_status,
906                value.receipt.revert_error,
907            )?,
908        })
909    }
910}
911
912impl TryFrom<ConfirmedReceiptWithContext> for core::DeployAccountTransactionReceipt {
913    type Error = ConversionError;
914
915    fn try_from(value: ConfirmedReceiptWithContext) -> Result<Self, Self::Error> {
916        Ok(Self {
917            transaction_hash: value.receipt.transaction_hash,
918            actual_fee: core::FeePayment {
919                amount: value.receipt.actual_fee,
920                unit: core::PriceUnit::Wei,
921            },
922            finality_status: value.finality.try_into()?,
923            messages_sent: value
924                .receipt
925                .l2_to_l1_messages
926                .into_iter()
927                .map(|item| item.into())
928                .collect(),
929            events: value
930                .receipt
931                .events
932                .into_iter()
933                .map(|item| item.into())
934                .collect(),
935            contract_address: match value.transaction {
936                TransactionType::DeployAccount(inner) => {
937                    inner.contract_address.ok_or(ConversionError)?
938                }
939                _ => return Err(ConversionError),
940            },
941            execution_resources: value
942                .receipt
943                .execution_resources
944                .ok_or(ConversionError)?
945                .total_gas_consumed
946                .ok_or(ConversionError)?,
947            execution_result: convert_execution_result(
948                value.receipt.execution_status,
949                value.receipt.revert_error,
950            )?,
951        })
952    }
953}
954
955impl TryFrom<ConfirmedReceiptWithContext> for core::InvokeTransactionReceipt {
956    type Error = ConversionError;
957
958    fn try_from(value: ConfirmedReceiptWithContext) -> Result<Self, Self::Error> {
959        Ok(Self {
960            transaction_hash: value.receipt.transaction_hash,
961            actual_fee: core::FeePayment {
962                amount: value.receipt.actual_fee,
963                unit: core::PriceUnit::Wei,
964            },
965            finality_status: value.finality.try_into()?,
966            messages_sent: value
967                .receipt
968                .l2_to_l1_messages
969                .into_iter()
970                .map(|item| item.into())
971                .collect(),
972            events: value
973                .receipt
974                .events
975                .into_iter()
976                .map(|item| item.into())
977                .collect(),
978            execution_resources: value
979                .receipt
980                .execution_resources
981                .ok_or(ConversionError)?
982                .total_gas_consumed
983                .ok_or(ConversionError)?,
984            execution_result: convert_execution_result(
985                value.receipt.execution_status,
986                value.receipt.revert_error,
987            )?,
988        })
989    }
990}
991
992impl TryFrom<ConfirmedReceiptWithContext> for core::L1HandlerTransactionReceipt {
993    type Error = ConversionError;
994
995    fn try_from(value: ConfirmedReceiptWithContext) -> Result<Self, Self::Error> {
996        // The sequencer never serves the message hash, so we have to compute it ourselves.
997        let l1_handler_tx: core::L1HandlerTransaction = match value.transaction {
998            TransactionType::L1Handler(tx) => tx.try_into().map_err(|_| ConversionError)?,
999            _ => return Err(ConversionError),
1000        };
1001        let msg_to_l2 = l1_handler_tx
1002            .parse_msg_to_l2()
1003            .map_err(|_| ConversionError)?;
1004
1005        Ok(Self {
1006            transaction_hash: value.receipt.transaction_hash,
1007            actual_fee: core::FeePayment {
1008                amount: value.receipt.actual_fee,
1009                unit: core::PriceUnit::Wei,
1010            },
1011            finality_status: value.finality.try_into()?,
1012            messages_sent: value
1013                .receipt
1014                .l2_to_l1_messages
1015                .into_iter()
1016                .map(|item| item.into())
1017                .collect(),
1018            events: value
1019                .receipt
1020                .events
1021                .into_iter()
1022                .map(|item| item.into())
1023                .collect(),
1024            execution_resources: value
1025                .receipt
1026                .execution_resources
1027                .ok_or(ConversionError)?
1028                .total_gas_consumed
1029                .ok_or(ConversionError)?,
1030            execution_result: convert_execution_result(
1031                value.receipt.execution_status,
1032                value.receipt.revert_error,
1033            )?,
1034            message_hash: msg_to_l2.hash(),
1035        })
1036    }
1037}
1038
1039impl TryFrom<BlockStatus> for core::TransactionFinalityStatus {
1040    type Error = ConversionError;
1041
1042    fn try_from(value: BlockStatus) -> Result<Self, Self::Error> {
1043        match value {
1044            // Transactions in pending blocks are considered "accepted on L2" now
1045            BlockStatus::Pending | BlockStatus::AcceptedOnL2 => Ok(Self::AcceptedOnL2),
1046            BlockStatus::AcceptedOnL1 => Ok(Self::AcceptedOnL1),
1047            BlockStatus::Aborted | BlockStatus::Reverted => Err(ConversionError),
1048        }
1049    }
1050}
1051
1052fn convert_execution_result(
1053    execution_status: Option<TransactionExecutionStatus>,
1054    revert_error: Option<String>,
1055) -> Result<core::ExecutionResult, ConversionError> {
1056    match (execution_status, revert_error) {
1057        (None, None) => {
1058            // This is a response from pre-v0.12.1. Pre-v0.12.1 transactions are always successful
1059            // as long as they're in a block.
1060            //
1061            // After feeder deprecation, the only way to fetch transaction receipts is by fetching
1062            // them as part of a block. Therefore, it's always the case that this tx in question is
1063            // in a block.
1064
1065            Ok(core::ExecutionResult::Succeeded)
1066        }
1067        (Some(TransactionExecutionStatus::Succeeded), None) => Ok(core::ExecutionResult::Succeeded),
1068        (Some(TransactionExecutionStatus::Reverted), Some(revert_error)) => {
1069            Ok(core::ExecutionResult::Reverted {
1070                reason: revert_error,
1071            })
1072        }
1073        // All other combinations are illegal
1074        _ => Err(ConversionError),
1075    }
1076}
1077
1078const fn convert_legacy_entry_point(
1079    value: core::LegacyContractEntryPoint,
1080) -> contract_legacy::RawLegacyEntryPoint {
1081    // WARNING: this causes pre-0.11.0 contract declaration to fail due to `offset` issue
1082    // TODO: support declaring pre-0.11.0 contracts here (do we even care though?)
1083    contract_legacy::RawLegacyEntryPoint {
1084        offset: contract_legacy::LegacyEntrypointOffset::U64AsInt(value.offset),
1085        selector: value.selector,
1086    }
1087}