starknet_devnet_server/api/
endpoints.rs

1use starknet_core::error::{ContractExecutionError, Error, StateError};
2use starknet_rs_core::types::{BlockId as ImportedBlockId, Felt, MsgFromL1};
3use starknet_rs_providers::Provider;
4use starknet_types::contract_address::ContractAddress;
5use starknet_types::felt::{ClassHash, TransactionHash};
6use starknet_types::patricia_key::PatriciaKey;
7use starknet_types::rpc::block::{
8    Block, BlockHeader, BlockId, BlockResult, BlockStatus, BlockTag, PreConfirmedBlock,
9    PreConfirmedBlockHeader,
10};
11use starknet_types::rpc::state::StateUpdateResult;
12use starknet_types::rpc::transaction_receipt::FeeUnit;
13use starknet_types::rpc::transactions::{
14    BroadcastedTransaction, EventFilter, EventsChunk, FunctionCall, SimulationFlag, Transactions,
15};
16
17use super::error::{ApiError, StrictRpcResult};
18use super::models::{
19    BlockHashAndNumberOutput, GetStorageProofInput, L1TransactionHashInput, SyncingOutput,
20};
21use crate::api::account_helpers::{
22    BalanceQuery, PredeployedAccountsQuery, get_balance, get_balance_unit,
23    get_erc20_fee_unit_address,
24};
25use crate::api::models::{
26    AccountBalanceResponse, AccountBalancesResponse, DevnetResponse, SerializableAccount,
27    StarknetResponse,
28};
29use crate::api::{JsonRpcHandler, RPC_SPEC_VERSION};
30use crate::config::DevnetConfig;
31
32const DEFAULT_CONTINUATION_TOKEN: &str = "0";
33const CONTINUATION_TOKEN_ORIGIN_PREFIX: &str = "devnet-origin-";
34
35/// The definitions of JSON-RPC read endpoints defined in starknet_api_openrpc.json
36impl JsonRpcHandler {
37    /// starknet_specVersion
38    pub fn spec_version(&self) -> StrictRpcResult {
39        Ok(StarknetResponse::String(RPC_SPEC_VERSION.to_string()).into())
40    }
41
42    /// starknet_getBlockWithTxHashes
43    pub async fn get_block_with_tx_hashes(&self, block_id: BlockId) -> StrictRpcResult {
44        let starknet = self.api.starknet.lock().await;
45
46        let block = starknet.get_block(&block_id).map_err(|err| match err {
47            Error::NoBlock => ApiError::BlockNotFound,
48            unknown_error => ApiError::StarknetDevnetError(unknown_error),
49        })?;
50
51        let transactions = Transactions::Hashes(block.get_transactions().to_owned());
52
53        Ok(match block.status() {
54            BlockStatus::PreConfirmed => StarknetResponse::PreConfirmedBlock(PreConfirmedBlock {
55                header: PreConfirmedBlockHeader::from(block),
56                transactions,
57            }),
58            _ => StarknetResponse::Block(Block {
59                status: *block.status(),
60                header: BlockHeader::from(block),
61                transactions,
62            }),
63        }
64        .into())
65    }
66
67    /// starknet_getBlockWithTxs
68    pub async fn get_block_with_txs(&self, block_id: BlockId) -> StrictRpcResult {
69        let block = self.api.starknet.lock().await.get_block_with_transactions(&block_id).map_err(
70            |err| match err {
71                Error::NoBlock => ApiError::BlockNotFound,
72                Error::NoTransaction => ApiError::TransactionNotFound,
73                unknown_error => ApiError::StarknetDevnetError(unknown_error),
74            },
75        )?;
76
77        match block {
78            BlockResult::Block(b) => Ok(StarknetResponse::Block(b).into()),
79            BlockResult::PreConfirmedBlock(b) => Ok(StarknetResponse::PreConfirmedBlock(b).into()),
80        }
81    }
82
83    /// starknet_getBlockWithReceipts
84    pub async fn get_block_with_receipts(&self, block_id: BlockId) -> StrictRpcResult {
85        let block = self.api.starknet.lock().await.get_block_with_receipts(&block_id).map_err(
86            |e| match e {
87                Error::NoBlock => ApiError::BlockNotFound,
88                Error::NoTransaction => ApiError::TransactionNotFound,
89                unknown_error => ApiError::StarknetDevnetError(unknown_error),
90            },
91        )?;
92
93        match block {
94            BlockResult::Block(b) => Ok(StarknetResponse::Block(b).into()),
95            BlockResult::PreConfirmedBlock(b) => Ok(StarknetResponse::PreConfirmedBlock(b).into()),
96        }
97    }
98
99    /// starknet_getStateUpdate
100    pub async fn get_state_update(&self, block_id: BlockId) -> StrictRpcResult {
101        let state_update =
102            self.api.starknet.lock().await.block_state_update(&block_id).map_err(|e| match e {
103                Error::NoBlock => ApiError::BlockNotFound,
104                unknown_error => ApiError::StarknetDevnetError(unknown_error),
105            })?;
106
107        match state_update {
108            StateUpdateResult::StateUpdate(s) => Ok(StarknetResponse::StateUpdate(s).into()),
109            StateUpdateResult::PreConfirmedStateUpdate(s) => {
110                Ok(StarknetResponse::PreConfirmedStateUpdate(s).into())
111            }
112        }
113    }
114
115    /// starknet_getStorageAt
116    pub async fn get_storage_at(
117        &self,
118        contract_address: ContractAddress,
119        key: PatriciaKey,
120        block_id: BlockId,
121    ) -> StrictRpcResult {
122        let felt = self
123            .api
124            .starknet
125            .lock()
126            .await
127            .contract_storage_at_block(&block_id, contract_address, key)
128            .map_err(|err| match err {
129                Error::NoBlock => ApiError::BlockNotFound,
130                Error::ContractNotFound | Error::StateError(StateError::NoneStorage(_)) => {
131                    ApiError::ContractNotFound
132                }
133                e @ Error::NoStateAtBlock { .. } => ApiError::NoStateAtBlock { msg: e.to_string() },
134                unknown_error => ApiError::StarknetDevnetError(unknown_error),
135            })?;
136
137        Ok(StarknetResponse::Felt(felt).into())
138    }
139
140    /// starknet_getStorageProof
141    pub async fn get_storage_proof(&self, data: GetStorageProofInput) -> StrictRpcResult {
142        match self.api.starknet.lock().await.get_block(&data.block_id) {
143            // storage proofs not applicable to Devnet
144            Ok(_) => Err(ApiError::StorageProofNotSupported),
145            Err(Error::NoBlock) => Err(ApiError::BlockNotFound),
146            Err(unknown_error) => Err(ApiError::StarknetDevnetError(unknown_error)),
147        }
148    }
149
150    /// starknet_getTransactionByHash
151    pub async fn get_transaction_by_hash(
152        &self,
153        transaction_hash: TransactionHash,
154    ) -> StrictRpcResult {
155        match self.api.starknet.lock().await.get_transaction_by_hash(transaction_hash) {
156            Ok(transaction) => Ok(StarknetResponse::Transaction(transaction.clone()).into()),
157            Err(Error::NoTransaction) => Err(ApiError::TransactionNotFound),
158            Err(err) => Err(err.into()),
159        }
160    }
161
162    /// starknet_getTransactionStatus
163    pub async fn get_transaction_status_by_hash(
164        &self,
165        transaction_hash: TransactionHash,
166    ) -> StrictRpcResult {
167        match self
168            .api
169            .starknet
170            .lock()
171            .await
172            .get_transaction_execution_and_finality_status(transaction_hash)
173        {
174            Ok(tx_status) => Ok(StarknetResponse::TransactionStatusByHash(tx_status).into()),
175            Err(Error::NoTransaction) => Err(ApiError::TransactionNotFound),
176            Err(err) => Err(err.into()),
177        }
178    }
179
180    /// starknet_getTransactionByBlockIdAndIndex
181    pub async fn get_transaction_by_block_id_and_index(
182        &self,
183        block_id: BlockId,
184        index: u64,
185    ) -> StrictRpcResult {
186        match self.api.starknet.lock().await.get_transaction_by_block_id_and_index(&block_id, index)
187        {
188            Ok(transaction) => Ok(StarknetResponse::Transaction(transaction.clone()).into()),
189            Err(Error::InvalidTransactionIndexInBlock) => {
190                Err(ApiError::InvalidTransactionIndexInBlock)
191            }
192            Err(Error::NoBlock) => Err(ApiError::BlockNotFound),
193            Err(unknown_error) => Err(ApiError::StarknetDevnetError(unknown_error)),
194        }
195    }
196
197    /// starknet_getTransactionReceipt
198    pub async fn get_transaction_receipt_by_hash(
199        &self,
200        transaction_hash: TransactionHash,
201    ) -> StrictRpcResult {
202        match self.api.starknet.lock().await.get_transaction_receipt_by_hash(&transaction_hash) {
203            Ok(receipt) => {
204                Ok(StarknetResponse::TransactionReceiptByTransactionHash(Box::new(receipt)).into())
205            }
206            Err(Error::NoTransaction) => Err(ApiError::TransactionNotFound),
207            Err(err) => Err(err.into()),
208        }
209    }
210
211    /// starknet_getClass
212    pub async fn get_class(&self, block_id: BlockId, class_hash: ClassHash) -> StrictRpcResult {
213        match self.api.starknet.lock().await.get_class(&block_id, class_hash) {
214            Ok(contract_class) => {
215                Ok(StarknetResponse::ContractClass(contract_class.try_into()?).into())
216            }
217            Err(e) => Err(match e {
218                Error::NoBlock => ApiError::BlockNotFound,
219                Error::StateError(_) => ApiError::ClassHashNotFound,
220                e @ Error::NoStateAtBlock { .. } => ApiError::NoStateAtBlock { msg: e.to_string() },
221                unknown_error => ApiError::StarknetDevnetError(unknown_error),
222            }),
223        }
224    }
225
226    /// starknet_getCompiledCasm
227    pub async fn get_compiled_casm(&self, class_hash: ClassHash) -> StrictRpcResult {
228        // starknet_getCompiledCasm compiles sierra to casm the same way it is done in
229        // starknet_addDeclareTransaction, so if during starknet_addDeclareTransaction compilation
230        // does not fail, so it will not fail during this endpoint execution
231        match self.api.starknet.lock().await.get_compiled_casm(class_hash) {
232            Ok(compiled_casm) => Ok(StarknetResponse::CompiledCasm(compiled_casm).into()),
233            Err(e) => Err(match e {
234                Error::NoBlock => ApiError::BlockNotFound,
235                Error::StateError(_) => ApiError::ClassHashNotFound,
236                e @ Error::NoStateAtBlock { .. } => ApiError::NoStateAtBlock { msg: e.to_string() },
237                unknown_error => ApiError::StarknetDevnetError(unknown_error),
238            }),
239        }
240    }
241
242    /// starknet_getClassAt
243    pub async fn get_class_at(
244        &self,
245        block_id: BlockId,
246        contract_address: ContractAddress,
247    ) -> StrictRpcResult {
248        match self.api.starknet.lock().await.get_class_at(&block_id, contract_address) {
249            Ok(contract_class) => {
250                Ok(StarknetResponse::ContractClass(contract_class.try_into()?).into())
251            }
252            Err(Error::NoBlock) => Err(ApiError::BlockNotFound),
253            Err(Error::StateError(StateError::NoneClassHash(_))) => {
254                // NoneClassHash can be returned only when forking, otherwise it means that
255                // contract_address is locally present, but its class hash isn't, which is a bug.
256                // ClassHashNotFound is not expected to be returned by the server, but to be handled
257                // by the forking logic as a signal to forward the request to the origin.
258                Err(ApiError::ClassHashNotFound)
259            }
260            Err(Error::ContractNotFound | Error::StateError(_)) => Err(ApiError::ContractNotFound),
261            Err(e @ Error::NoStateAtBlock { .. }) => {
262                Err(ApiError::NoStateAtBlock { msg: e.to_string() })
263            }
264            Err(unknown_error) => Err(ApiError::StarknetDevnetError(unknown_error)),
265        }
266    }
267
268    /// starknet_getClassHashAt
269    pub async fn get_class_hash_at(
270        &self,
271        block_id: BlockId,
272        contract_address: ContractAddress,
273    ) -> StrictRpcResult {
274        match self.api.starknet.lock().await.get_class_hash_at(&block_id, contract_address) {
275            Ok(class_hash) => Ok(StarknetResponse::Felt(class_hash).into()),
276            Err(Error::NoBlock) => Err(ApiError::BlockNotFound),
277            Err(Error::ContractNotFound) => Err(ApiError::ContractNotFound),
278            Err(e @ Error::NoStateAtBlock { .. }) => {
279                Err(ApiError::NoStateAtBlock { msg: e.to_string() })
280            }
281            Err(unknown_error) => Err(ApiError::StarknetDevnetError(unknown_error)),
282        }
283    }
284
285    /// starknet_getBlockTransactionCount
286    pub async fn get_block_txs_count(&self, block_id: BlockId) -> StrictRpcResult {
287        let num_trans_count = self.api.starknet.lock().await.get_block_txs_count(&block_id);
288        match num_trans_count {
289            Ok(count) => Ok(StarknetResponse::BlockTransactionCount(count).into()),
290            Err(_) => Err(ApiError::BlockNotFound),
291        }
292    }
293
294    /// starknet_call
295    pub async fn call(&self, block_id: BlockId, request: FunctionCall) -> StrictRpcResult {
296        match self.api.starknet.lock().await.call(
297            &block_id,
298            request.contract_address.into(),
299            request.entry_point_selector,
300            request.calldata,
301        ) {
302            Ok(result) => Ok(StarknetResponse::Call(result).into()),
303            Err(Error::NoBlock) => Err(ApiError::BlockNotFound),
304            Err(Error::ContractNotFound) => Err(ApiError::ContractNotFound),
305            Err(Error::EntrypointNotFound) => Err(ApiError::EntrypointNotFound),
306            Err(e @ Error::NoStateAtBlock { .. }) => {
307                Err(ApiError::NoStateAtBlock { msg: e.to_string() })
308            }
309            Err(Error::ContractExecutionError(execution_error)) => {
310                Err(ApiError::ContractError(execution_error))
311            }
312            Err(e) => Err(ApiError::ContractError(ContractExecutionError::Message(e.to_string()))),
313        }
314    }
315
316    /// starknet_estimateFee
317    pub async fn estimate_fee(
318        &self,
319        block_id: BlockId,
320        request: Vec<BroadcastedTransaction>,
321        simulation_flags: Vec<SimulationFlag>,
322    ) -> StrictRpcResult {
323        match self.api.starknet.lock().await.estimate_fee(&block_id, &request, &simulation_flags) {
324            Ok(result) => Ok(StarknetResponse::EstimateFee(result).into()),
325            Err(Error::ContractNotFound) => Err(ApiError::ContractNotFound),
326            Err(Error::NoBlock) => Err(ApiError::BlockNotFound),
327            Err(e @ Error::NoStateAtBlock { .. }) => {
328                Err(ApiError::NoStateAtBlock { msg: e.to_string() })
329            }
330            Err(Error::ContractExecutionErrorInSimulation { failure_index, execution_error }) => {
331                Err(ApiError::TransactionExecutionError { failure_index, execution_error })
332            }
333            Err(e) => Err(ApiError::ContractError(ContractExecutionError::from(e.to_string()))),
334        }
335    }
336
337    pub async fn estimate_message_fee(
338        &self,
339        block_id: &BlockId,
340        message: MsgFromL1,
341    ) -> StrictRpcResult {
342        match self.api.starknet.lock().await.estimate_message_fee(block_id, message) {
343            Ok(result) => Ok(StarknetResponse::EstimateMessageFee(result).into()),
344            Err(Error::ContractNotFound) => Err(ApiError::ContractNotFound),
345            Err(Error::NoBlock) => Err(ApiError::BlockNotFound),
346            Err(e @ Error::NoStateAtBlock { .. }) => {
347                Err(ApiError::NoStateAtBlock { msg: e.to_string() })
348            }
349            Err(Error::ContractExecutionError(error)) => Err(ApiError::ContractError(error)),
350            Err(e) => Err(ApiError::ContractError(ContractExecutionError::from(e.to_string()))),
351        }
352    }
353
354    /// starknet_blockNumber
355    pub async fn block_number(&self) -> StrictRpcResult {
356        let block = self.api.starknet.lock().await.get_latest_block().map_err(|err| match err {
357            Error::NoBlock => ApiError::BlockNotFound,
358            unknown_error => ApiError::StarknetDevnetError(unknown_error),
359        })?;
360
361        Ok(StarknetResponse::BlockNumber(block.block_number()).into())
362    }
363
364    /// starknet_blockHashAndNumber
365    pub async fn block_hash_and_number(&self) -> StrictRpcResult {
366        let block = self.api.starknet.lock().await.get_latest_block().map_err(|err| match err {
367            Error::NoBlock => ApiError::BlockNotFound,
368            unknown_error => ApiError::StarknetDevnetError(unknown_error),
369        })?;
370
371        Ok(StarknetResponse::BlockHashAndNumber(BlockHashAndNumberOutput {
372            block_hash: block.block_hash(),
373            block_number: block.block_number(),
374        })
375        .into())
376    }
377
378    /// starknet_chainId
379    pub async fn chain_id(&self) -> StrictRpcResult {
380        let chain_id = self.api.starknet.lock().await.chain_id();
381
382        Ok(StarknetResponse::Felt(chain_id.to_felt()).into())
383    }
384
385    /// starknet_syncing
386    pub async fn syncing(&self) -> StrictRpcResult {
387        Ok(StarknetResponse::Syncing(SyncingOutput::False(false)).into())
388    }
389
390    /// Split into origin and local block ranges (non overlapping, local continuing onto origin)
391    /// Returns: (origin_range, local_start, local_end)
392    /// All ranges inclusive
393    async fn split_block_range(
394        &self,
395        from_block: Option<BlockId>,
396        to_block: Option<BlockId>,
397    ) -> Result<(Option<(u64, u64)>, Option<BlockId>, Option<BlockId>), ApiError> {
398        let origin_caller = match &self.origin_caller {
399            Some(origin_caller) => origin_caller,
400            None => return Ok((None, from_block, to_block)),
401        };
402
403        let fork_block_number = origin_caller.fork_block_number();
404
405        let starknet = self.api.starknet.lock().await;
406
407        let from_block_number = match from_block {
408            Some(BlockId::Tag(BlockTag::Latest | BlockTag::PreConfirmed)) => {
409                return Ok((None, from_block, to_block));
410            }
411            Some(block_id @ (BlockId::Tag(BlockTag::L1Accepted) | BlockId::Hash(_))) => {
412                match starknet.get_block(&block_id) {
413                    Ok(block) => block.block_number().0,
414                    Err(_) => origin_caller.get_block_number_from_block_id(block_id).await?,
415                }
416            }
417            Some(BlockId::Number(from_block_number)) => from_block_number,
418            None => 0, // If no from_block, all blocks before to_block should be queried
419        };
420
421        if from_block_number > fork_block_number {
422            // Only local blocks need to be searched
423            return Ok((None, Some(BlockId::Number(from_block_number)), to_block));
424        }
425
426        let to_block_number = match to_block {
427            // If to_block is latest, pre_confirmed or undefined, all blocks after from_block are
428            // queried
429            Some(BlockId::Tag(BlockTag::Latest | BlockTag::PreConfirmed)) | None => {
430                return Ok((
431                    Some((from_block_number, fork_block_number)),
432                    // there is for sure at least one local block
433                    Some(BlockId::Number(fork_block_number + 1)),
434                    to_block,
435                ));
436            }
437            Some(block_id @ (BlockId::Tag(BlockTag::L1Accepted) | BlockId::Hash(_))) => {
438                match starknet.get_block(&block_id) {
439                    Ok(block) => block.block_number().0,
440                    Err(_) => origin_caller.get_block_number_from_block_id(block_id).await?,
441                }
442            }
443            Some(BlockId::Number(to_block_number)) => to_block_number,
444        };
445
446        let origin_range = Some((from_block_number, to_block_number));
447        Ok(if to_block_number <= fork_block_number {
448            (origin_range, None, None)
449        } else {
450            (
451                origin_range,
452                Some(BlockId::Number(fork_block_number + 1)),
453                Some(BlockId::Number(to_block_number)),
454            )
455        })
456    }
457
458    /// Fetches events from forking origin. The continuation token should be the same as received by
459    /// Devnet (not yet adapted for origin). If more events can be fetched from the origin, this is
460    /// noted in the `continuation_token` of the returned `EventsChunk`.
461    pub(crate) async fn fetch_origin_events_chunk(
462        &self,
463        from_origin: u64,
464        to_origin: u64,
465        continuation_token: Option<String>,
466        address: Option<ContractAddress>,
467        keys: Option<Vec<Vec<Felt>>>,
468        chunk_size: u64,
469    ) -> Result<EventsChunk, ApiError> {
470        let origin_caller = self.origin_caller.as_ref().ok_or(ApiError::StarknetDevnetError(
471            Error::UnexpectedInternalError { msg: "Origin caller unexpectedly undefined".into() },
472        ))?;
473
474        let origin_continuation_token = continuation_token
475            .map(|token| token.trim_start_matches(CONTINUATION_TOKEN_ORIGIN_PREFIX).to_string());
476
477        let mut origin_events_chunk: EventsChunk = origin_caller
478            .starknet_client
479            .get_events(
480                starknet_rs_core::types::EventFilter {
481                    from_block: Some(ImportedBlockId::Number(from_origin)),
482                    to_block: Some(ImportedBlockId::Number(to_origin)),
483                    address: address.map(|address| address.into()),
484                    keys,
485                },
486                origin_continuation_token,
487                chunk_size,
488            )
489            .await
490            .map_err(|e| {
491                ApiError::StarknetDevnetError(Error::UnexpectedInternalError {
492                    msg: format!("Error in fetching origin events: {e:?}"),
493                })
494            })?
495            .into();
496
497        // If origin has no more chunks, set the token to default, which will signalize the
498        // switch to querying the local state on next request.
499        origin_events_chunk.continuation_token = origin_events_chunk
500            .continuation_token
501            .map_or(Some(DEFAULT_CONTINUATION_TOKEN.to_owned()), |token| {
502                Some(CONTINUATION_TOKEN_ORIGIN_PREFIX.to_owned() + &token)
503            });
504
505        Ok(origin_events_chunk)
506    }
507
508    /// starknet_getEvents
509    pub async fn get_events(&self, filter: EventFilter) -> StrictRpcResult {
510        let (origin_range, from_local_block_id, to_local_block_id) =
511            self.split_block_range(filter.from_block, filter.to_block).await?;
512
513        // Get events either from forking origin or locally
514        let events_chunk = if origin_range.is_some()
515            && filter
516                .continuation_token
517                .clone()
518                .is_none_or(|token| token.starts_with(CONTINUATION_TOKEN_ORIGIN_PREFIX))
519        {
520            #[allow(clippy::unnecessary_unwrap)]
521            #[allow(clippy::expect_used)]
522            let (from_origin, to_origin) =
523                origin_range.expect("Continuation token implies there are more origin events");
524
525            self.fetch_origin_events_chunk(
526                from_origin,
527                to_origin,
528                filter.continuation_token,
529                filter.address,
530                filter.keys,
531                filter.chunk_size,
532            )
533            .await?
534        } else {
535            let pages_read_so_far = filter
536                .continuation_token
537                .unwrap_or(DEFAULT_CONTINUATION_TOKEN.to_string())
538                .parse::<u64>()
539                .map_err(|_| ApiError::InvalidContinuationToken)?;
540
541            let starknet = self.api.starknet.lock().await;
542            let (events, has_more_events) = starknet
543                .get_events(
544                    from_local_block_id,
545                    to_local_block_id,
546                    filter.address,
547                    filter.keys,
548                    None,
549                    pages_read_so_far * filter.chunk_size,
550                    Some(filter.chunk_size),
551                )
552                .map_err(|e| match e {
553                    Error::NoBlock => ApiError::BlockNotFound,
554                    _ => e.into(),
555                })?;
556
557            EventsChunk {
558                events,
559                continuation_token: has_more_events.then(|| (pages_read_so_far + 1).to_string()),
560            }
561        };
562
563        Ok(StarknetResponse::Events(events_chunk).into())
564    }
565
566    /// starknet_getNonce
567    pub async fn get_nonce(
568        &self,
569        block_id: BlockId,
570        contract_address: ContractAddress,
571    ) -> StrictRpcResult {
572        let nonce = self
573            .api
574            .starknet
575            .lock()
576            .await
577            .contract_nonce_at_block(&block_id, contract_address)
578            .map_err(|err| match err {
579                Error::NoBlock => ApiError::BlockNotFound,
580                Error::ContractNotFound => ApiError::ContractNotFound,
581                e @ Error::NoStateAtBlock { .. } => ApiError::NoStateAtBlock { msg: e.to_string() },
582                unknown_error => ApiError::StarknetDevnetError(unknown_error),
583            })?;
584
585        Ok(StarknetResponse::Felt(nonce).into())
586    }
587
588    /// starknet_simulateTransactions
589    pub async fn simulate_transactions(
590        &self,
591        block_id: BlockId,
592        transactions: Vec<BroadcastedTransaction>,
593        simulation_flags: Vec<SimulationFlag>,
594    ) -> StrictRpcResult {
595        let mut starknet = self.api.starknet.lock().await;
596
597        match starknet.simulate_transactions(&block_id, &transactions, simulation_flags) {
598            Ok(result) => Ok(StarknetResponse::SimulateTransactions(result).into()),
599            Err(Error::ContractNotFound) => Err(ApiError::ContractNotFound),
600            Err(Error::NoBlock) => Err(ApiError::BlockNotFound),
601            Err(e @ Error::NoStateAtBlock { .. }) => {
602                Err(ApiError::NoStateAtBlock { msg: e.to_string() })
603            }
604            Err(Error::ContractExecutionErrorInSimulation { failure_index, execution_error }) => {
605                Err(ApiError::TransactionExecutionError { failure_index, execution_error })
606            }
607            Err(e) => Err(ApiError::ContractError(ContractExecutionError::from(e.to_string()))),
608        }
609    }
610
611    /// starknet_traceTransaction
612    pub async fn get_trace_transaction(
613        &self,
614        transaction_hash: TransactionHash,
615    ) -> StrictRpcResult {
616        let starknet = self.api.starknet.lock().await;
617        match starknet.get_transaction_trace_by_hash(transaction_hash) {
618            Ok(result) => Ok(StarknetResponse::TraceTransaction(result).into()),
619            Err(Error::NoTransaction) => Err(ApiError::TransactionNotFound),
620            Err(Error::UnsupportedTransactionType) => Err(ApiError::NoTraceAvailable),
621            Err(err) => Err(err.into()),
622        }
623    }
624
625    /// starknet_traceBlockTransactions
626    pub async fn get_trace_block_transactions(&self, block_id: BlockId) -> StrictRpcResult {
627        let starknet = self.api.starknet.lock().await;
628        match starknet.get_transaction_traces_from_block(&block_id) {
629            Ok(result) => Ok(StarknetResponse::BlockTransactionTraces(result).into()),
630            Err(Error::NoBlock) => Err(ApiError::BlockNotFound),
631            Err(err) => Err(err.into()),
632        }
633    }
634
635    /// starknet_getMessagesStatus
636    pub async fn get_messages_status(
637        &self,
638        L1TransactionHashInput { transaction_hash }: L1TransactionHashInput,
639    ) -> StrictRpcResult {
640        let starknet = self.api.starknet.lock().await;
641        match starknet.get_messages_status(transaction_hash) {
642            Some(statuses) => Ok(StarknetResponse::MessagesStatusByL1Hash(statuses).into()),
643            None => Err(ApiError::TransactionNotFound),
644        }
645    }
646
647    /// devnet_getPredeployedAccounts
648    pub async fn get_predeployed_accounts(
649        &self,
650        params: Option<PredeployedAccountsQuery>,
651    ) -> StrictRpcResult {
652        let mut starknet = self.api.starknet.lock().await;
653        let mut predeployed_accounts: Vec<_> = starknet
654            .get_predeployed_accounts()
655            .into_iter()
656            .map(|acc| SerializableAccount {
657                initial_balance: acc.initial_balance.to_string(),
658                address: acc.account_address,
659                public_key: acc.keys.public_key,
660                private_key: acc.keys.private_key,
661                balance: None,
662            })
663            .collect();
664
665        // handle with_balance query string
666        if let Some(true) =
667            params.unwrap_or(PredeployedAccountsQuery { with_balance: Option::None }).with_balance
668        {
669            for account in predeployed_accounts.iter_mut() {
670                let eth = get_balance_unit(&mut starknet, account.address, FeeUnit::WEI)?;
671                let strk = get_balance_unit(&mut starknet, account.address, FeeUnit::FRI)?;
672
673                account.balance = Some(AccountBalancesResponse { eth, strk });
674            }
675        }
676        Ok(DevnetResponse::PredeployedAccounts(predeployed_accounts).into())
677    }
678
679    /// devnet_getAccountBalance
680    pub async fn get_account_balance(&self, params: BalanceQuery) -> StrictRpcResult {
681        let account_address = ContractAddress::new(params.address)
682            .map_err(|e| ApiError::InvalidAddress { msg: e.to_string() })?;
683        let unit = params.unit.unwrap_or(FeeUnit::FRI);
684        let erc20_address = get_erc20_fee_unit_address(&unit);
685
686        let mut starknet = self.api.starknet.lock().await;
687
688        let amount = get_balance(
689            &mut starknet,
690            account_address,
691            erc20_address,
692            params.block_id.unwrap_or(BlockId::Tag(BlockTag::Latest)),
693        )?;
694        Ok(DevnetResponse::AccountBalance(AccountBalanceResponse {
695            amount: amount.to_string(),
696            unit,
697        })
698        .into())
699    }
700
701    /// devnet_getConfig
702    pub async fn get_devnet_config(&self) -> StrictRpcResult {
703        Ok(DevnetResponse::DevnetConfig(DevnetConfig {
704            starknet_config: (*self.api.config).clone(),
705            server_config: (*self.api.server_config).clone(),
706        })
707        .into())
708    }
709}