Repository: 0xSpaceShard/starknet-devnet (language: python)

Issue:
Refactor JSON-RPC blueprint
This issue concerns the refactor of `blueprints/rpc.py`. The test refactor has been requested with #145.
L1->L2 & L2->L1 messages
Has any thought been given to how these should be supported yet?


Discussion / hints:

In fact just yesterday I was "giving thought" to this issue while going through this: https://www.cairo-lang.org/docs/hello_starknet/l1l2.html.

To be clear, the Alpha testnet, which this devnet aims to mimic, is able to communicate with an Ethereum testnet (currently Goerli). And currently, the devnet is isolated from L1 chains.

Do you have an idea on how this could be addressed? Or have you found the relevant starknet code parts concerning the L1-L2 connection?
> Do you have an idea on how this could be addressed?

One possibility could be to try to set up a local Ethereum devnet (e.g. ganache) such that L1->L2 and L2->L1 messages can be passed between them. That way, there'd be a totally local devnet which replicates what can be done on the Goerli testnet.

I'm not sure how much integration work/complexity/maintenance would be required.


Barring total integration, we could add an endpoint to `starknet-devnet` that calls `starknet.send_message_to_l2` (as in this example: https://github.com/makerdao/starknet-dai-bridge/blob/mk/draft/test/l2/l2_dai_bridge.py#L288)

I suppose a broader question is: should `starknet-devnet` be solely about replicating the testnet deployment locally, or should it also be about exposing Starkware's testing framework more broadly? If the former, then including `send_message_to_l2` like this might not make sense. If the latter, then it could be a good addition.
> One possibility could be to try to set up a local Ethereum devnet (e.g. ganache) such that L1->L2 and L2->L1 messages can be passed between them. That way, there'd be a totally local devnet which replicates what can be done on the Goerli testnet.

Yes, that also occurred to me. But it could be more generic: just providing the RPC endpoint of the testnet you wish your devnet instance to be connected with, be it something local like Ganache, or something nonlocal like Goerli. Something like passing `--l1_endpoint https://goerli...` on devnet startup.

I'd say this is a good enhancement idea and I'd keep this issue open until a proper PR is merged.
This has been addressed with [v0.1.15](https://github.com/Shard-Labs/starknet-devnet/releases/tag/v0.1.15). There are now dedicated endpoints for specifying the L1 address and flushing the L1->L2 and L2->L1 communication channels. Handlers for these endpoints are being added to `starknet-hardhat-plugin`.

Fix patch:
diff --git a/README.md b/README.md
index 83347f840..09107e706 100644
--- a/README.md
+++ b/README.md
@@ -169,14 +169,16 @@ If you don't specify the `HOST` part, the server will indeed be available on all
 
 ## JSON-RPC API
 
-Devnet also partially supports JSON-RPC API (v0.15.0: [specifications](https://github.com/starkware-libs/starknet-specs/blob/606c21e06be92ea1543fd0134b7f98df622c2fbf/api/starknet_api_openrpc.json)) and WRITE API (v0.3.0: [specifications](https://github.com/starkware-libs/starknet-specs/blob/4c31d6f9f842028ca8cfd073ec8d0d5089b087c4/api/starknet_write_api.json)). It can be reached under `/rpc`. For an example:
+Devnet also partially supports JSON-RPC API v0.1.0: [specifications](https://github.com/starkware-libs/starknet-specs/releases/tag/v0.1.0) . It can be reached under `/rpc`. For an example:
 
 ```
 POST /rpc
 {
   "jsonrpc": "2.0",
-  "method": "starknet_protocolVersion",
-  "params": [],
+  "method": "starknet_getBlockTransactionCount",
+  "params": {
+    "block_id": "latest"
+  },
   "id": 0
 }
 ```
@@ -187,10 +189,15 @@ Response:
 {
   "id": 0,
   "jsonrpc": "2.0",
-  "result": "0x302e382e30"
+  "result": 1
 }
 ```
 
+Currently not supported methods are:
+- `starknet_protocolVersion` - will be removed in a future version of the specification
+- `starknet_getEvents`
+- `starknet_getNonce`
+
 ## Hardhat integration
 
 If you're using [the Hardhat plugin](https://github.com/Shard-Labs/starknet-hardhat-plugin), see [here](https://github.com/Shard-Labs/starknet-hardhat-plugin#runtime-network) on how to edit its config file to integrate Devnet.
diff --git a/starknet_devnet/blueprints/rpc.py b/starknet_devnet/blueprints/rpc.py
deleted file mode 100644
index e0aa2e152..000000000
--- a/starknet_devnet/blueprints/rpc.py
+++ /dev/null
@@ -1,1035 +0,0 @@
-"""
-RPC routes
-rpc version: 0.15.0
-"""
-# pylint: disable=too-many-lines
-
-from __future__ import annotations
-import dataclasses
-import json
-
-from typing import Callable, Union, List, Tuple, Optional, Any
-from typing_extensions import TypedDict
-from flask import Blueprint, request
-from marshmallow.exceptions import MarshmallowError
-
-from starkware.starknet.services.api.contract_class import ContractClass
-from starkware.starkware_utils.error_handling import StarkException
-from starkware.starknet.definitions import constants
-from starkware.starknet.services.api.gateway.transaction import (
-    DECLARE_SENDER_ADDRESS,
-    Declare,
-    Deploy,
-    InvokeFunction,
-)
-from starkware.starknet.services.api.gateway.transaction_utils import compress_program, decompress_program
-from starkware.starknet.services.api.feeder_gateway.response_objects import (
-    StarknetBlock,
-    InvokeSpecificInfo,
-    DeploySpecificInfo,
-    TransactionReceipt,
-    TransactionStatus,
-    TransactionSpecificInfo,
-    TransactionType,
-    BlockStateUpdate,
-    DeclareSpecificInfo
-)
-
-from starknet_devnet.state import state
-from ..util import StarknetDevnetException
-
-rpc = Blueprint("rpc", __name__, url_prefix="/rpc")
-
-PROTOCOL_VERSION = "0.15.0"
-
-
-@rpc.route("", methods=["POST"])
-async def base_route():
-    """
-    Base route for RPC calls
-    """
-    method, args, message_id = parse_body(request.json)
-
-    try:
-        result = await method(*args) if isinstance(args, list) else await method(**args)
-    except NotImplementedError:
-        return rpc_error(message_id=message_id, code=-2, message="Method not implemented")
-    except RpcError as error:
-        return rpc_error(message_id=message_id, code=error.code, message=error.message)
-
-    return rpc_response(message_id=message_id, content=result)
-
-
-async def get_block_by_hash(block_hash: str, requested_scope: str = "TXN_HASH") -> dict:
-    """
-    Get block information given the block id
-    """
-    try:
-        result = state.starknet_wrapper.blocks.get_by_hash(block_hash=block_hash)
-    except StarknetDevnetException as ex:
-        raise RpcError(code=24, message="Invalid block hash") from ex
-
-    return await rpc_block(block=result, requested_scope=requested_scope)
-
-
-async def get_block_by_number(block_number: int, requested_scope: str = "TXN_HASH") -> dict:
-    """
-    Get block information given the block number (its height)
-    """
-    try:
-        result = state.starknet_wrapper.blocks.get_by_number(block_number=block_number)
-    except StarknetDevnetException as ex:
-        raise RpcError(code=26, message="Invalid block number") from ex
-
-    return await rpc_block(block=result, requested_scope=requested_scope)
-
-
-async def get_state_update_by_hash(block_hash: str) -> dict:
-    """
-    Get the information about the result of executing the requested block
-    """
-    try:
-        result = state.starknet_wrapper.blocks.get_state_update(block_hash=block_hash)
-    except StarknetDevnetException as ex:
-        raise RpcError(code=24, message="Invalid block hash") from ex
-
-    return rpc_state_update(result)
-
-
-async def get_storage_at(contract_address: str, key: str, block_hash: str) -> str:
-    """
-    Get the value of the storage at the given address and key
-    """
-    if block_hash != "latest":
-        # By RPC here we should return `24 invalid block hash` but in this case I believe it's more
-        # descriptive to the user to use a custom error
-        raise RpcError(code=-1, message="Calls with block_hash != 'latest' are not supported currently.")
-
-    if not state.starknet_wrapper.contracts.is_deployed(int(contract_address, 16)):
-        raise RpcError(code=20, message="Contract not found")
-
-    return await state.starknet_wrapper.get_storage_at(
-        contract_address=int(contract_address, 16),
-        key=int(key, 16)
-    )
-
-
-async def get_transaction_by_hash(transaction_hash: str) -> dict:
-    """
-    Get the details and status of a submitted transaction
-    """
-    try:
-        result = state.starknet_wrapper.transactions.get_transaction(transaction_hash)
-    except StarknetDevnetException as ex:
-        raise RpcError(code=25, message="Invalid transaction hash") from ex
-
-    if result.status == TransactionStatus.NOT_RECEIVED:
-        raise RpcError(code=25, message="Invalid transaction hash")
-
-    return rpc_transaction(result.transaction)
-
-
-async def get_transaction_by_block_hash_and_index(block_hash: str, index: int) -> dict:
-    """
-    Get the details of a transaction by a given block hash and index
-    """
-    try:
-        block = state.starknet_wrapper.blocks.get_by_hash(block_hash=block_hash)
-    except StarknetDevnetException as ex:
-        raise RpcError(code=24, message="Invalid block hash") from ex
-
-    try:
-        transaction_hash: int = block.transactions[index].transaction_hash
-        return await get_transaction_by_hash(transaction_hash=rpc_felt(transaction_hash))
-    except IndexError as ex:
-        raise RpcError(code=27, message="Invalid transaction index in a block") from ex
-
-
-async def get_transaction_by_block_number_and_index(block_number: int, index: int) -> dict:
-    """
-    Get the details of a transaction by a given block number and index
-    """
-    try:
-        block = state.starknet_wrapper.blocks.get_by_number(block_number=block_number)
-    except StarknetDevnetException as ex:
-        raise RpcError(code=26, message="Invalid block number") from ex
-
-    try:
-        transaction_hash: int = block.transactions[index].transaction_hash
-        return await get_transaction_by_hash(transaction_hash=rpc_felt(transaction_hash))
-    except IndexError as ex:
-        raise RpcError(code=27, message="Invalid transaction index in a block") from ex
-
-
-async def get_transaction_receipt(transaction_hash: str) -> dict:
-    """
-    Get the transaction receipt by the transaction hash
-    """
-    try:
-        result = state.starknet_wrapper.transactions.get_transaction_receipt(tx_hash=transaction_hash)
-    except StarknetDevnetException as ex:
-        raise RpcError(code=25, message="Invalid transaction hash") from ex
-
-    if result.status == TransactionStatus.NOT_RECEIVED:
-        raise RpcError(code=25, message="Invalid transaction hash")
-
-    return rpc_transaction_receipt(result)
-
-
-async def get_code(contract_address: str) -> dict:
-    """
-    Get the code of a specific contract
-    """
-    try:
-        result = state.starknet_wrapper.contracts.get_code(address=int(contract_address, 16))
-    except StarknetDevnetException as ex:
-        raise RpcError(code=20, message="Contract not found") from ex
-
-    if len(result["bytecode"]) == 0:
-        raise RpcError(code=20, message="Contract not found")
-
-    return {
-        "bytecode": result["bytecode"],
-        "abi": json.dumps(result["abi"])
-    }
-
-
-async def get_class(class_hash: str) -> dict:
-    """
-    Get the code of a specific contract
-    """
-    try:
-        result = state.starknet_wrapper.contracts.get_class_by_hash(class_hash=int(class_hash, 16))
-    except StarknetDevnetException as ex:
-        raise RpcError(code=28, message="The supplied contract class hash is invalid or unknown") from ex
-
-    return rpc_contract_class(result)
-
-
-async def get_class_hash_at(contract_address: str) -> str:
-    """
-    Get the contract class hash for the contract deployed at the given address
-    """
-    try:
-        result = state.starknet_wrapper.contracts.get_class_hash_at(address=int(contract_address, 16))
-    except StarknetDevnetException as ex:
-        raise RpcError(code=28, message="The supplied contract class hash is invalid or unknown") from ex
-
-    return rpc_felt(result)
-
-
-async def get_class_at(contract_address: str) -> dict:
-    """
-    Get the contract class definition at the given address
-    """
-    try:
-        class_hash = state.starknet_wrapper.contracts.get_class_hash_at(address=int(contract_address, 16))
-        result = state.starknet_wrapper.contracts.get_class_by_hash(class_hash=class_hash)
-    except StarknetDevnetException as ex:
-        raise RpcError(code=20, message="Contract not found") from ex
-
-    return rpc_contract_class(result)
-
-
-async def get_block_transaction_count_by_hash(block_hash: str) -> int:
-    """
-    Get the number of transactions in a block given a block hash
-    """
-    try:
-        block = state.starknet_wrapper.blocks.get_by_hash(block_hash=block_hash)
-        return len(block.transactions)
-    except StarknetDevnetException as ex:
-        raise RpcError(code=24, message="Invalid block hash") from ex
-
-
-async def get_block_transaction_count_by_number(block_number: int) -> int:
-    """
-    Get the number of transactions in a block given a block number (height)
-    """
-    try:
-        block = state.starknet_wrapper.blocks.get_by_number(block_number=block_number)
-        return len(block.transactions)
-    except StarknetDevnetException as ex:
-        raise RpcError(code=26, message="Invalid block number") from ex
-
-
-async def call(contract_address: str, entry_point_selector: str, calldata: list, block_hash: str = "") -> list:
-    """
-    Call a starknet function without creating a StarkNet transaction
-    """
-    request_body = {
-        "contract_address": contract_address,
-        "entry_point_selector": entry_point_selector,
-        "calldata": calldata
-    }
-
-    # For now, we only support 'latest' block, support for specific blocks
-    # in devnet is more complicated if possible at all
-    if block_hash != "latest":
-        # By RPC here we should return `24 invalid block hash` but in this case I believe it's more
-        # descriptive to the user to use a custom error
-        raise RpcError(code=-1, message="Calls with block_hash != 'latest' are not supported currently.")
-
-    if not state.starknet_wrapper.contracts.is_deployed(int(contract_address, 16)):
-        raise RpcError(code=20, message="Contract not found")
-
-    try:
-        return await state.starknet_wrapper.call(transaction=make_invoke_function(request_body))
-    except StarknetDevnetException as ex:
-        raise RpcError(code=-1, message=ex.message) from ex
-    except StarkException as ex:
-        if f"Entry point {entry_point_selector} not found" in ex.message:
-            raise RpcError(code=21, message="Invalid message selector") from ex
-        if "While handling calldata" in ex.message:
-            raise RpcError(code=22, message="Invalid call data") from ex
-        raise RpcError(code=-1, message=ex.message) from ex
-
-
-async def estimate_fee(request_body: dict):
-    """
-    Get the estimate fee for the transaction
-    """
-    raise NotImplementedError()
-
-
-async def get_block_number() -> int:
-    """
-    Get the most recent accepted block number
-    """
-    result = state.starknet_wrapper.blocks.get_number_of_blocks() - 1
-    return result if result >= 0 else 0
-
-
-async def chain_id() -> str:
-    """
-    Return the currently configured StarkNet chain id
-    """
-    devnet_state = state.starknet_wrapper.get_state()
-    config = devnet_state.general_config
-    chain: int = config.chain_id.value
-    return hex(chain)
-
-
-async def pending_transactions():
-    """
-    Returns the transactions in the transaction pool, recognized by this sequencer
-    """
-    raise NotImplementedError()
-
-
-async def protocol_version() -> str:
-    """
-    Returns the current starknet protocol version identifier, as supported by this sequencer
-    """
-    protocol_hex = PROTOCOL_VERSION.encode("utf-8").hex()
-    return "0x" + protocol_hex
-
-
-async def syncing():
-    """
-    Returns an object about the sync status, or false if the node is not synching
-    """
-    raise NotImplementedError()
-
-
-async def get_events():
-    """
-    Returns all events matching the given filter
-    """
-    raise NotImplementedError()
-
-
-async def add_invoke_transaction(function_invocation: dict, signature: List[str], max_fee: str, version: str) -> dict:
-    """
-    Submit a new transaction to be added to the chain
-    """
-    invoke_function = InvokeFunction(
-        contract_address=int(function_invocation["contract_address"], 16),
-        entry_point_selector=int(function_invocation["entry_point_selector"], 16),
-        calldata=[int(data, 16) for data in function_invocation["calldata"]],
-        max_fee=int(max_fee, 16),
-        version=int(version, 16),
-        signature=[int(data, 16) for data in signature],
-    )
-
-    _, transaction_hash, _ = await state.starknet_wrapper.invoke(invoke_function=invoke_function)
-    return RpcInvokeTransactionResult(
-        transaction_hash=rpc_felt(transaction_hash),
-    )
-
-
-async def add_declare_transaction(contract_class: RpcContractClass, version: str) -> dict:
-    """
-    Submit a new class declaration transaction
-    """
-    try:
-        decompressed_program = decompress_program({"contract_class": contract_class}, False)["contract_class"]
-        contract_definition = ContractClass.load(decompressed_program)
-
-        # Replace None with [] in abi key to avoid Missing Abi exception
-        contract_definition = dataclasses.replace(contract_definition, abi=[])
-    except (StarkException, TypeError, MarshmallowError) as ex:
-        raise RpcError(code=50, message="Invalid contract class") from ex
-
-    declare_transaction = Declare(
-        contract_class=contract_definition,
-        version=int(version, 16),
-        sender_address=DECLARE_SENDER_ADDRESS,
-        max_fee=0,
-        signature=[],
-        nonce=0,
-    )
-
-    class_hash, transaction_hash = await state.starknet_wrapper.declare(declare_transaction=declare_transaction)
-    return RpcDeclareTransactionResult(
-        transaction_hash=rpc_felt(transaction_hash),
-        class_hash=rpc_felt(class_hash),
-    )
-
-
-async def add_deploy_transaction(contract_address_salt: str, constructor_calldata: List[str], contract_definition: RpcContractClass) -> dict:
-    """
-    Submit a new deploy contract transaction
-    """
-    try:
-        decompressed_program = decompress_program({"contract_definition": contract_definition}, False)["contract_definition"]
-        contract_class = ContractClass.load(decompressed_program)
-        contract_class = dataclasses.replace(contract_class, abi=[])
-    except (StarkException, TypeError, MarshmallowError) as ex:
-        raise RpcError(code=50, message="Invalid contract class") from ex
-
-    deploy_transaction = Deploy(
-        contract_address_salt=int(contract_address_salt, 16),
-        constructor_calldata=[int(data, 16) for data in constructor_calldata],
-        contract_definition=contract_class,
-        version=constants.TRANSACTION_VERSION,
-    )
-
-    contract_address, transaction_hash = await state.starknet_wrapper.deploy(deploy_transaction=deploy_transaction)
-    return RpcDeployTransactionResult(
-        transaction_hash=rpc_felt(transaction_hash),
-        contract_address=rpc_felt(contract_address),
-    )
-
-
-def make_invoke_function(request_body: dict) -> InvokeFunction:
-    """
-    Convert RPC request to internal InvokeFunction
-    """
-    return InvokeFunction(
-        contract_address=int(request_body["contract_address"], 16),
-        entry_point_selector=int(request_body["entry_point_selector"], 16),
-        calldata=[int(data, 16) for data in request_body["calldata"]],
-        max_fee=0,
-        version=0,
-        signature=[],
-    )
-
-
-class EntryPoint(TypedDict):
-    """
-    TypedDict for rpc contract class entry point
-    """
-    offset: str
-    selector: str
-
-
-class EntryPoints(TypedDict):
-    """
-    TypedDict for rpc contract class entry points
-    """
-    CONSTRUCTOR: List[EntryPoint]
-    EXTERNAL: List[EntryPoint]
-    L1_HANDLER: List[EntryPoint]
-
-
-class RpcContractClass(TypedDict):
-    """
-    TypedDict for rpc contract class
-    """
-    program: str
-    entry_points_by_type: EntryPoints
-
-
-def rpc_contract_class(contract_class: ContractClass) -> RpcContractClass:
-    """
-    Convert gateway contract class to rpc contract class
-    """
-    def program() -> str:
-        _program = contract_class.program.Schema().dump(contract_class.program)
-        return compress_program(_program)
-
-    def entry_points_by_type() -> EntryPoints:
-        _entry_points: EntryPoints = {
-            "CONSTRUCTOR": [],
-            "EXTERNAL": [],
-            "L1_HANDLER": [],
-        }
-        for typ, entry_points in contract_class.entry_points_by_type.items():
-            for entry_point in entry_points:
-                _entry_point: EntryPoint = {
-                    "selector": rpc_felt(entry_point.selector),
-                    "offset": rpc_felt(entry_point.offset)
-                }
-                _entry_points[typ.name].append(_entry_point)
-        return _entry_points
-
-    _contract_class: RpcContractClass = {
-        "program": program(),
-        "entry_points_by_type": entry_points_by_type()
-    }
-    return _contract_class
-
-
-class RpcBlock(TypedDict):
-    """
-    TypeDict for rpc block
-    """
-    block_hash: str
-    parent_hash: str
-    block_number: int
-    status: str
-    sequencer_address: str
-    new_root: str
-    timestamp: int
-    transactions: Union[List[str], List[dict]]
-
-
-async def rpc_block(block: StarknetBlock, requested_scope: Optional[str] = "TXN_HASH") -> RpcBlock:
-    """
-    Convert gateway block to rpc block
-    """
-    async def transactions() -> List[Union[RpcInvokeTransaction, RpcDeclareTransaction]]:
-        # pylint: disable=no-member
-        return [rpc_transaction(tx) for tx in block.transactions]
-
-    async def transaction_hashes() -> List[str]:
-        return [tx["txn_hash"] for tx in await transactions()]
-
-    async def full_transactions() -> list[dict[str, Any]]:
-        transactions_and_receipts = []
-        _transactions = await transactions()
-        for transaction in _transactions:
-            receipt = await get_transaction_receipt(transaction["txn_hash"])
-            combined = {**receipt, **transaction}
-            transactions_and_receipts.append(combined)
-        return transactions_and_receipts
-
-    def new_root() -> str:
-        # pylint: disable=no-member
-        return rpc_root(block.state_root.hex())
-
-    mapping: dict[str, Callable] = {
-        "TXN_HASH": transaction_hashes,
-        "FULL_TXNS": transactions,
-        "FULL_TXN_AND_RECEIPTS": full_transactions,
-    }
-    transactions: list = await mapping[requested_scope]()
-
-    devnet_state = state.starknet_wrapper.get_state()
-    config = devnet_state.general_config
-
-    block: RpcBlock = {
-        "block_hash": rpc_felt(block.block_hash),
-        "parent_hash": rpc_felt(block.parent_block_hash) or "0x0",
-        "block_number": block.block_number if block.block_number is not None else 0,
-        "status": block.status.name,
-        "sequencer_address": hex(config.sequencer_address),
-        "new_root": new_root(),
-        "timestamp": block.timestamp,
-        "transactions": transactions,
-    }
-    return block
-
-
-class RpcStorageDiff(TypedDict):
-    """
-    TypedDict for rpc storage diff
-    """
-    address: str
-    key: str
-    value: str
-
-
-class RpcContractDiff(TypedDict):
-    """
-    TypedDict for rpc contract diff
-    """
-    address: str
-    contract_hash: str
-
-
-class RpcNonceDiff(TypedDict):
-    """
-    TypedDict for rpc nonce diff
-    """
-    contract_address: str
-    nonce: str
-
-
-class RpcStateDiff(TypedDict):
-    """
-    TypedDict for rpc state diff
-    """
-    storage_diffs: List[RpcStorageDiff]
-    contracts: List[RpcContractDiff]
-    nonces: List[RpcNonceDiff]
-
-
-class RpcStateUpdate(TypedDict):
-    """
-    TypedDict for rpc state update
-    """
-    block_hash: str
-    new_root: str
-    old_root: str
-    accepted_time: int
-    state_diff: RpcStateDiff
-
-
-def rpc_state_update(state_update: BlockStateUpdate) -> RpcStateUpdate:
-    """
-    Convert gateway state update to rpc state update
-    """
-    def storage_diffs() -> List[RpcStorageDiff]:
-        _storage_diffs = []
-        for address, diffs in state_update.state_diff.storage_diffs.items():
-            for diff in diffs:
-                _diff: RpcStorageDiff = {
-                    "address": rpc_felt(address),
-                    "key": rpc_felt(diff.key),
-                    "value": rpc_felt(diff.value),
-                }
-                _storage_diffs.append(_diff)
-        return _storage_diffs
-
-    def contracts() -> List[RpcContractDiff]:
-        _contracts = []
-        for contract in state_update.state_diff.deployed_contracts:
-            diff: RpcContractDiff = {
-                "address": rpc_felt(contract.address),
-                "contract_hash": rpc_felt(contract.class_hash)
-            }
-            _contracts.append(diff)
-        return _contracts
-
-    def timestamp() -> int:
-        block = state.starknet_wrapper.blocks.get_by_hash(block_hash=hex(state_update.block_hash))
-        return block.timestamp
-
-    rpc_state: RpcStateUpdate = {
-        "block_hash": rpc_felt(state_update.block_hash),
-        "new_root": rpc_root(state_update.new_root.hex()),
-        "old_root": rpc_root(state_update.old_root.hex()),
-        "accepted_time": timestamp(),
-        "state_diff": {
-            "storage_diffs": storage_diffs(),
-            "contracts": contracts(),
-            "nonces": [],
-        }
-    }
-    return rpc_state
-
-
-def rpc_state_diff_contract(contract: dict) -> dict:
-    """
-    Convert gateway contract state diff to rpc contract state diff
-    """
-    return {
-        "address": contract["address"],
-        "contract_hash": f"0x{contract['contract_hash']}",
-    }
-
-
-def rpc_state_diff_storage(contract: dict) -> dict:
-    """
-    Convert gateway storage state diff to rpc storage state diff
-    """
-    return {
-        "address": contract["address"],
-        "key": contract["key"],
-        "value": contract["value"],
-    }
-
-
-class RpcInvokeTransaction(TypedDict):
-    """
-    TypedDict for rpc invoke transaction
-    """
-    contract_address: str
-    entry_point_selector: Optional[str]
-    calldata: Optional[List[str]]
-    # Common
-    txn_hash: str
-    max_fee: str
-    version: str
-    signature: List[str]
-
-
-class RpcDeclareTransaction(TypedDict):
-    """
-    TypedDict for rpc declare transaction
-    """
-    contract_class: RpcContractClass
-    sender_address: str
-    # Common
-    txn_hash: str
-    max_fee: str
-    version: str
-    signature: List[str]
-
-
-class RpcInvokeTransactionResult(TypedDict):
-    """
-    TypedDict for rpc invoke transaction result
-    """
-    transaction_hash: str
-
-
-class RpcDeclareTransactionResult(TypedDict):
-    """
-    TypedDict for rpc declare transaction result
-    """
-    transaction_hash: str
-    class_hash: str
-
-
-class RpcDeployTransactionResult(TypedDict):
-    """
-    TypedDict for rpc deploy transaction result
-    """
-    transaction_hash: str
-    contract_address: str
-
-
-def rpc_invoke_transaction(transaction: InvokeSpecificInfo) -> RpcInvokeTransaction:
-    """
-    Convert gateway invoke transaction to rpc format
-    """
-    transaction: RpcInvokeTransaction = {
-        "contract_address": rpc_felt(transaction.contract_address),
-        "entry_point_selector": rpc_felt(transaction.entry_point_selector),
-        "calldata": [rpc_felt(data) for data in transaction.calldata],
-        "max_fee": rpc_felt(transaction.max_fee),
-        "txn_hash": rpc_felt(transaction.transaction_hash),
-        "version": hex(0x0),
-        "signature": [rpc_felt(value) for value in transaction.signature]
-    }
-    return transaction
-
-
-def rpc_deploy_transaction(transaction: DeploySpecificInfo) -> RpcInvokeTransaction:
-    """
-    Convert gateway deploy transaction to rpc format
-    """
-    transaction: RpcInvokeTransaction = {
-        "contract_address": rpc_felt(transaction.contract_address),
-        "entry_point_selector": None,
-        "calldata": [rpc_felt(data) for data in transaction.constructor_calldata],
-        "max_fee": rpc_felt(0x0),
-        "txn_hash": rpc_felt(transaction.transaction_hash),
-        "version": hex(0x0),
-        "signature": []
-    }
-    return transaction
-
-
-def rpc_declare_transaction(transaction: DeclareSpecificInfo) -> RpcDeclareTransaction:
-    """
-    Convert gateway declare transaction to rpc format
-    """
-    def contract_class() -> RpcContractClass:
-        # pylint: disable=no-member
-        _contract_claass = state.starknet_wrapper.contracts.get_class_by_hash(transaction.class_hash)
-        return rpc_contract_class(_contract_claass)
-
-    transaction: RpcDeclareTransaction = {
-        "contract_class": contract_class(),
-        "sender_address": rpc_felt(transaction.sender_address),
-        "max_fee": rpc_felt(transaction.max_fee),
-        "txn_hash": rpc_felt(transaction.transaction_hash),
-        "version": hex(transaction.version),
-        "signature": [rpc_felt(value) for value in transaction.signature]
-    }
-    return transaction
-
-
-def rpc_transaction(transaction: TransactionSpecificInfo) -> Union[RpcInvokeTransaction, RpcDeclareTransaction]:
-    """
-    Convert gateway transaction to rpc transaction
-    """
-    tx_mapping = {
-        TransactionType.DEPLOY: rpc_deploy_transaction,
-        TransactionType.INVOKE_FUNCTION: rpc_invoke_transaction,
-        TransactionType.DECLARE: rpc_declare_transaction,
-    }
-    return tx_mapping[transaction.tx_type](transaction)
-
-
-class MessageToL1(TypedDict):
-    """
-    TypedDict for rpc message from l2 to l1
-    """
-    to_address: str
-    payload: List[str]
-
-
-class MessageToL2(TypedDict):
-    """
-    TypedDict for rpc message from l1 to l2
-    """
-    from_address: str
-    payload: List[str]
-
-
-class Event(TypedDict):
-    """
-    TypedDict for rpc event
-    """
-    from_address: str
-    keys: List[str]
-    data: List[str]
-
-
-class RpcBaseTransactionReceipt(TypedDict):
-    """
-    TypedDict for rpc transaction receipt
-    """
-    # Common
-    txn_hash: str
-    actual_fee: str
-    status: str
-    statusData: Optional[str]
-
-
-class RpcInvokeReceipt(TypedDict):
-    """
-    TypedDict for rpc invoke transaction receipt
-    """
-    messages_sent: List[MessageToL1]
-    l1_origin_message: Optional[MessageToL2]
-    events: List[Event]
-    # Common
-    txn_hash: str
-    actual_fee: str
-    status: str
-    statusData: Optional[str]
-
-
-class RpcDeclareReceipt(TypedDict):
-    """
-    TypedDict for rpc declare transaction receipt
-    """
-    # Common
-    txn_hash: str
-    actual_fee: str
-    status: str
-    statusData: Optional[str]
-
-
-def rpc_invoke_receipt(txr: TransactionReceipt) -> RpcInvokeReceipt:
-    """
-    Convert rpc invoke transaction receipt to rpc format
-    """
-    def l2_to_l1_messages() -> List[MessageToL1]:
-        messages = []
-        for message in txr.l2_to_l1_messages:
-            msg: MessageToL1 = {
-                "to_address": message.to_address,
-                "payload": [rpc_felt(p) for p in message.payload]
-            }
-            messages.append(msg)
-        return messages
-
-    def l1_to_l2_message() -> Optional[MessageToL2]:
-        if txr.l1_to_l2_consumed_message is None:
-            return None
-
-        msg: MessageToL2 = {
-            "from_address": txr.l1_to_l2_consumed_message.from_address,
-            "payload": [rpc_felt(p) for p in txr.l1_to_l2_consumed_message.payload]
-        }
-        return msg
-
-    def events() -> List[Event]:
-        _events = []
-        for event in txr.events:
-            event: Event = {
-                "from_address": rpc_felt(event.from_address),
-                "keys": [rpc_felt(e) for e in event.keys],
-                "data": [rpc_felt(d) for d in event.data],
-            }
-            _events.append(event)
-        return _events
-
-    base_receipt = rpc_base_transaction_receipt(txr)
-    receipt: RpcInvokeReceipt = {
-        "messages_sent": l2_to_l1_messages(),
-        "l1_origin_message": l1_to_l2_message(),
-        "events": events(),
-        "txn_hash": base_receipt["txn_hash"],
-        "status": base_receipt["status"],
-        "statusData": base_receipt["statusData"],
-        "actual_fee": base_receipt["actual_fee"],
-    }
-    return receipt
-
-
-def rpc_declare_receipt(txr) -> RpcDeclareReceipt:
-    """
-    Convert rpc declare transaction receipt to rpc format
-    """
-    base_receipt = rpc_base_transaction_receipt(txr)
-    receipt: RpcDeclareReceipt = {
-        "txn_hash": base_receipt["txn_hash"],
-        "status": base_receipt["status"],
-        "statusData": base_receipt["statusData"],
-        "actual_fee": base_receipt["actual_fee"],
-    }
-    return receipt
-
-
-def rpc_deploy_receipt(txr) -> RpcBaseTransactionReceipt:
-    """
-    Convert rpc deploy transaction receipt to rpc format
-    """
-    return rpc_base_transaction_receipt(txr)
-
-
-def rpc_base_transaction_receipt(txr: TransactionReceipt) -> RpcBaseTransactionReceipt:
-    """
-    Convert gateway transaction receipt to rpc transaction receipt
-    """
-    def status() -> str:
-        if txr.status is None:
-            return "UNKNOWN"
-
-        mapping = {
-            TransactionStatus.NOT_RECEIVED: "UNKNOWN",
-            TransactionStatus.ACCEPTED_ON_L2: "ACCEPTED_ON_L2",
-            TransactionStatus.ACCEPTED_ON_L1: "ACCEPTED_ON_L1",
-            TransactionStatus.RECEIVED: "RECEIVED",
-            TransactionStatus.PENDING: "PENDING",
-            TransactionStatus.REJECTED: "REJECTED",
-        }
-        return mapping[txr.status]
-
-    def status_data() -> Union[str, None]:
-        if txr.transaction_failure_reason is not None:
-            if txr.transaction_failure_reason.error_message is not None:
-                return txr.transaction_failure_reason.error_message
-        return None
-
-    receipt: RpcBaseTransactionReceipt = {
-        "txn_hash": rpc_felt(txr.transaction_hash),
-        "status": status(),
-        "statusData": status_data(),
-        "actual_fee": rpc_felt(txr.actual_fee or 0),
-    }
-    return receipt
-
-
-def rpc_transaction_receipt(txr: TransactionReceipt) -> dict:
-    """
-    Convert gateway transaction receipt to rpc format
-    """
-    tx_mapping = {
-        TransactionType.DEPLOY: rpc_deploy_receipt,
-        TransactionType.INVOKE_FUNCTION: rpc_invoke_receipt,
-        TransactionType.DECLARE: rpc_declare_receipt,
-    }
-    transaction = state.starknet_wrapper.transactions.get_transaction(hex(txr.transaction_hash)).transaction
-    tx_type = transaction.tx_type
-    return tx_mapping[tx_type](txr)
-
-
-def rpc_response(message_id: int, content: dict) -> dict:
-    """
-    Wrap response content in rpc format
-    """
-    return {
-        "jsonrpc": "2.0",
-        "id": message_id,
-        "result": content
-    }
-
-
-def rpc_error(message_id: int, code: int, message: str) -> dict:
-    """
-    Wrap error in rpc format
-    """
-    return {
-        "jsonrpc": "2.0",
-        "id": message_id,
-        "error": {
-            "code": code,
-            "message": message
-        }
-    }
-
-
-def rpc_felt(value: int) -> str:
-    """
-    Convert integer to 0x0 prefixed felt
-    """
-    return "0x0" + hex(value).lstrip("0x")
-
-
-def rpc_root(root: str) -> str:
-    """
-    Convert 0 prefixed root to 0x prefixed root
-    """
-    root = root[1:]
-    return "0x0" + root
-
-
-def parse_body(body: dict) -> Tuple[Callable, Union[List, dict], int]:
-    """
-    Parse rpc call body to function name and params
-    """
-    methods = {
-        "getBlockByNumber": get_block_by_number,
-        "getBlockByHash": get_block_by_hash,
-        "getStateUpdateByHash": get_state_update_by_hash,
-        "getStorageAt": get_storage_at,
-        "getTransactionByHash": get_transaction_by_hash,
-        "getTransactionByBlockHashAndIndex": get_transaction_by_block_hash_and_index,
-        "getTransactionByBlockNumberAndIndex": get_transaction_by_block_number_and_index,
-        "getTransactionReceipt": get_transaction_receipt,
-        "getCode": get_code,
-        "getBlockTransactionCountByHash": get_block_transaction_count_by_hash,
-        "getBlockTransactionCountByNumber": get_block_transaction_count_by_number,
-        "call": call,
-        "blockNumber": get_block_number,
-        "chainId": chain_id,
-        "pendingTransactions": pending_transactions,
-        "protocolVersion": protocol_version,
-        "syncing": syncing,
-        "getEvents": get_events,
-        "getClass": get_class,
-        "getClassHashAt": get_class_hash_at,
-        "getClassAt": get_class_at,
-        "estimateFee": estimate_fee,
-        "addInvokeTransaction": add_invoke_transaction,
-        "addDeclareTransaction": add_declare_transaction,
-        "addDeployTransaction": add_deploy_transaction,
-    }
-    method_name = body["method"].replace("starknet_", "")
-    args: Union[List, dict] = body["params"]
-    message_id = body["id"]
-
-    if method_name not in methods:
-        raise RpcError(code=-1, message="Method not found")
-
-    return methods[method_name], args, message_id
-
-
-class RpcError(Exception):
-    """
-    Error message returned by rpc
-    """
-
-    def __init__(self, code, message):
-        super().__init__(message)
-        self.code = code
-        self.message = message
diff --git a/starknet_devnet/blueprints/rpc/__init__.py b/starknet_devnet/blueprints/rpc/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/starknet_devnet/blueprints/rpc/blocks.py b/starknet_devnet/blueprints/rpc/blocks.py
new file mode 100644
index 000000000..b4fb47d01
--- /dev/null
+++ b/starknet_devnet/blueprints/rpc/blocks.py
@@ -0,0 +1,61 @@
+"""
+RPC block endpoints
+"""
+
+from starknet_devnet.blueprints.rpc.structures.payloads import rpc_block
+from starknet_devnet.blueprints.rpc.structures.types import BlockId, RpcError
+from starknet_devnet.blueprints.rpc.utils import get_block_by_block_id, rpc_felt
+from starknet_devnet.state import state
+
+
+async def get_block_with_tx_hashes(block_id: BlockId) -> dict:
+    """
+    Get block information with transaction hashes given the block id
+    """
+    block = get_block_by_block_id(block_id)
+    return await rpc_block(block=block)
+
+
+async def get_block_with_txs(block_id: BlockId) -> dict:
+    """
+    Get block information with full transactions given the block id
+    """
+    block = get_block_by_block_id(block_id)
+    return await rpc_block(block=block, tx_type="FULL_TXNS")
+
+
+async def block_number() -> int:
+    """
+    Get the most recent accepted block number
+    """
+    number_of_blocks = state.starknet_wrapper.blocks.get_number_of_blocks()
+    if number_of_blocks == 0:
+        raise RpcError(code=32, message="There are no blocks")
+
+    return number_of_blocks - 1
+
+
+async def block_hash_and_number() -> dict:
+    """
+    Get the most recent accepted block hash and number
+    """
+    number_of_blocks = state.starknet_wrapper.blocks.get_number_of_blocks()
+    if number_of_blocks == 0:
+        raise RpcError(code=32, message="There are no blocks")
+
+    last_block_number = number_of_blocks - 1
+    last_block = state.starknet_wrapper.blocks.get_by_number(last_block_number)
+
+    result = {
+        "block_hash": rpc_felt(last_block.block_hash),
+        "block_number": last_block.block_number,
+    }
+    return result
+
+
+async def get_block_transaction_count(block_id: BlockId) -> int:
+    """
+    Get the number of transactions in a block given a block id
+    """
+    block = get_block_by_block_id(block_id)
+    return len(block.transactions)
diff --git a/starknet_devnet/blueprints/rpc/call.py b/starknet_devnet/blueprints/rpc/call.py
new file mode 100644
index 000000000..d63fe6529
--- /dev/null
+++ b/starknet_devnet/blueprints/rpc/call.py
@@ -0,0 +1,36 @@
+"""
+RPC call endpoint
+"""
+
+from typing import List
+
+from starkware.starkware_utils.error_handling import StarkException
+
+from starknet_devnet.blueprints.rpc.utils import rpc_felt, assert_block_id_is_latest
+from starknet_devnet.blueprints.rpc.structures.payloads import make_invoke_function, FunctionCall
+from starknet_devnet.blueprints.rpc.structures.types import Felt, BlockId, RpcError
+from starknet_devnet.state import state
+from starknet_devnet.util import StarknetDevnetException
+
+
+async def call(request: FunctionCall, block_id: BlockId) -> List[Felt]:
+    """
+    Call a starknet function without creating a StarkNet transaction
+    """
+    assert_block_id_is_latest(block_id)
+
+    if not state.starknet_wrapper.contracts.is_deployed(int(request["contract_address"], 16)):
+        raise RpcError(code=20, message="Contract not found")
+
+    try:
+        result = await state.starknet_wrapper.call(transaction=make_invoke_function(request))
+        result["result"] = [rpc_felt(int(res, 16)) for res in result["result"]]
+        return result
+    except StarknetDevnetException as ex:
+        raise RpcError(code=-1, message=ex.message) from ex
+    except StarkException as ex:
+        if f"Entry point {request['entry_point_selector']} not found" in ex.message:
+            raise RpcError(code=21, message="Invalid message selector") from ex
+        if "While handling calldata" in ex.message:
+            raise RpcError(code=22, message="Invalid call data") from ex
+        raise RpcError(code=-1, message=ex.message) from ex
diff --git a/starknet_devnet/blueprints/rpc/classes.py b/starknet_devnet/blueprints/rpc/classes.py
new file mode 100644
index 000000000..4905fb6de
--- /dev/null
+++ b/starknet_devnet/blueprints/rpc/classes.py
@@ -0,0 +1,50 @@
+"""
+RPC classes endpoints
+"""
+
+from starknet_devnet.blueprints.rpc.utils import assert_block_id_is_latest, rpc_felt
+from starknet_devnet.blueprints.rpc.structures.payloads import rpc_contract_class
+from starknet_devnet.blueprints.rpc.structures.types import BlockId, Address, Felt, RpcError
+from starknet_devnet.state import state
+from starknet_devnet.util import StarknetDevnetException
+
+
+async def get_class(class_hash: Felt) -> dict:
+    """
+    Get the contract class definition associated with the given hash
+    """
+    try:
+        result = state.starknet_wrapper.contracts.get_class_by_hash(class_hash=int(class_hash, 16))
+    except StarknetDevnetException as ex:
+        raise RpcError(code=28, message="The supplied contract class hash is invalid or unknown") from ex
+
+    return rpc_contract_class(result)
+
+
+async def get_class_hash_at(block_id: BlockId, contract_address: Address) -> Felt:
+    """
+    Get the contract class hash in the given block for the contract deployed at the given address
+    """
+    assert_block_id_is_latest(block_id)
+
+    try:
+        result = state.starknet_wrapper.contracts.get_class_hash_at(address=int(contract_address, 16))
+    except StarknetDevnetException as ex:
+        raise RpcError(code=28, message="The supplied contract class hash is invalid or unknown") from ex
+
+    return rpc_felt(result)
+
+
+async def get_class_at(block_id: BlockId, contract_address: Address) -> dict:
+    """
+    Get the contract class definition in the given block at the given address
+    """
+    assert_block_id_is_latest(block_id)
+
+    try:
+        class_hash = state.starknet_wrapper.contracts.get_class_hash_at(address=int(contract_address, 16))
+        result = state.starknet_wrapper.contracts.get_class_by_hash(class_hash=class_hash)
+    except StarknetDevnetException as ex:
+        raise RpcError(code=20, message="Contract not found") from ex
+
+    return rpc_contract_class(result)
diff --git a/starknet_devnet/blueprints/rpc/misc.py b/starknet_devnet/blueprints/rpc/misc.py
new file mode 100644
index 000000000..6cc4a1e74
--- /dev/null
+++ b/starknet_devnet/blueprints/rpc/misc.py
@@ -0,0 +1,44 @@
+"""
+RPC miscellaneous endpoints
+"""
+
+from __future__ import annotations
+
+from typing import Union
+
+from starknet_devnet.blueprints.rpc.structures.payloads import Felt, Address
+from starknet_devnet.state import state
+
+
+async def chain_id() -> str:
+    """
+    Return the currently configured StarkNet chain id
+    """
+    devnet_state = state.starknet_wrapper.get_state()
+    config = devnet_state.general_config
+    chain: int = config.chain_id.value
+    return hex(chain)
+
+
+async def syncing() -> Union[dict, bool]:
+    """
+    Returns an object about the sync status, or false if the node is not synching
+    """
+    return False
+
+
+# pylint: disable=unused-argument
+# pylint: disable=redefined-builtin
+async def get_events(filter: dict) -> dict:
+    """
+    Returns all events matching the given filter
+    """
+    raise NotImplementedError()
+
+
+# pylint: disable=unused-argument
+async def get_nonce(contract_address: Address) -> Felt:
+    """
+    Get the latest nonce associated with the given address
+    """
+    raise NotImplementedError()
diff --git a/starknet_devnet/blueprints/rpc/routes.py b/starknet_devnet/blueprints/rpc/routes.py
new file mode 100644
index 000000000..213570ec7
--- /dev/null
+++ b/starknet_devnet/blueprints/rpc/routes.py
@@ -0,0 +1,84 @@
+"""
+RPC base route
+API Specification v0.1.0
+https://github.com/starkware-libs/starknet-specs/releases/tag/v0.1.0
+"""
+
+from __future__ import annotations
+
+from typing import Callable, Union, List, Tuple
+
+from flask import Blueprint
+from flask import request
+
+from starknet_devnet.blueprints.rpc.blocks import get_block_with_tx_hashes, get_block_with_txs, \
+    get_block_transaction_count, block_number, block_hash_and_number
+from starknet_devnet.blueprints.rpc.call import call
+from starknet_devnet.blueprints.rpc.classes import get_class, get_class_hash_at, get_class_at
+from starknet_devnet.blueprints.rpc.misc import chain_id, syncing, get_events, get_nonce
+from starknet_devnet.blueprints.rpc.state import get_state_update
+from starknet_devnet.blueprints.rpc.storage import get_storage_at
+from starknet_devnet.blueprints.rpc.transactions import get_transaction_by_hash, \
+    get_transaction_by_block_id_and_index, get_transaction_receipt, estimate_fee, pending_transactions, \
+    add_invoke_transaction, add_declare_transaction, add_deploy_transaction
+from starknet_devnet.blueprints.rpc.utils import rpc_response, rpc_error
+from starknet_devnet.blueprints.rpc.structures.types import RpcError
+
+methods = {
+    "getBlockWithTxHashes": get_block_with_tx_hashes,
+    "getBlockWithTxs": get_block_with_txs,
+    "getStateUpdate": get_state_update,
+    "getStorageAt": get_storage_at,
+    "getTransactionByHash": get_transaction_by_hash,
+    "getTransactionByBlockIdAndIndex": get_transaction_by_block_id_and_index,
+    "getTransactionReceipt": get_transaction_receipt,
+    "getClass": get_class,
+    "getClassHashAt": get_class_hash_at,
+    "getClassAt": get_class_at,
+    "getBlockTransactionCount": get_block_transaction_count,
+    "call": call,
+    "estimateFee": estimate_fee,
+    "blockNumber": block_number,
+    "blockHashAndNumber": block_hash_and_number,
+    "chainId": chain_id,
+    "pendingTransactions": pending_transactions,
+    "syncing": syncing,
+    "getEvents": get_events,
+    "getNonce": get_nonce,
+    "addInvokeTransaction": add_invoke_transaction,
+    "addDeclareTransaction": add_declare_transaction,
+    "addDeployTransaction": add_deploy_transaction,
+}
+
+rpc = Blueprint("rpc", __name__, url_prefix="/rpc")
+
+
+@rpc.route("", methods=["POST"])
+async def base_route():
+    """
+    Base route for RPC calls
+    """
+    method, args, message_id = parse_body(request.json)
+
+    try:
+        result = await method(*args) if isinstance(args, list) else await method(**args)
+    except NotImplementedError:
+        return rpc_error(message_id=message_id, code=-2, message="Method not implemented")
+    except RpcError as error:
+        return rpc_error(message_id=message_id, code=error.code, message=error.message)
+
+    return rpc_response(message_id=message_id, content=result)
+
+
+def parse_body(body: dict) -> Tuple[Callable, Union[List, dict], int]:
+    """
+    Parse rpc call body to function name and params
+    """
+    method_name = body["method"].replace("starknet_", "")
+    args: Union[List, dict] = body["params"]
+    message_id = body["id"]
+
+    if method_name not in methods:
+        raise RpcError(code=-1, message="Method not found")
+
+    return methods[method_name], args, message_id
diff --git a/starknet_devnet/blueprints/rpc/state.py b/starknet_devnet/blueprints/rpc/state.py
new file mode 100644
index 000000000..d45ea203c
--- /dev/null
+++ b/starknet_devnet/blueprints/rpc/state.py
@@ -0,0 +1,26 @@
+"""
+RPC state endpoints
+"""
+
+from starknet_devnet.blueprints.rpc.utils import block_tag_to_block_number
+from starknet_devnet.blueprints.rpc.structures.payloads import rpc_state_update
+from starknet_devnet.blueprints.rpc.structures.types import BlockId, RpcError
+from starknet_devnet.state import state
+from starknet_devnet.util import StarknetDevnetException
+
+
+async def get_state_update(block_id: BlockId) -> dict:
+    """
+    Get the information about the result of executing the requested block
+    """
+    block_id = block_tag_to_block_number(block_id)
+
+    try:
+        if "block_hash" in block_id:
+            result = state.starknet_wrapper.blocks.get_state_update(block_hash=block_id["block_hash"])
+        else:
+            result = state.starknet_wrapper.blocks.get_state_update(block_number=block_id["block_number"])
+    except StarknetDevnetException as ex:
+        raise RpcError(code=24, message="Invalid block id") from ex
+
+    return rpc_state_update(result)
diff --git a/starknet_devnet/blueprints/rpc/storage.py b/starknet_devnet/blueprints/rpc/storage.py
new file mode 100644
index 000000000..e90382ae2
--- /dev/null
+++ b/starknet_devnet/blueprints/rpc/storage.py
@@ -0,0 +1,23 @@
+"""
+RPC storage endpoints
+"""
+
+from starknet_devnet.blueprints.rpc.utils import assert_block_id_is_latest, rpc_felt
+from starknet_devnet.blueprints.rpc.structures.types import Address, BlockId, Felt, RpcError
+from starknet_devnet.state import state
+
+
+async def get_storage_at(contract_address: Address, key: str, block_id: BlockId) -> Felt:
+    """
+    Get the value of the storage at the given address and key
+    """
+    assert_block_id_is_latest(block_id)
+
+    if not state.starknet_wrapper.contracts.is_deployed(int(contract_address, 16)):
+        raise RpcError(code=20, message="Contract not found")
+
+    storage = await state.starknet_wrapper.get_storage_at(
+        contract_address=int(contract_address, 16),
+        key=int(key, 16)
+    )
+    return rpc_felt(int(storage, 16))
diff --git a/starknet_devnet/blueprints/rpc/structures/__init__.py b/starknet_devnet/blueprints/rpc/structures/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/starknet_devnet/blueprints/rpc/structures/payloads.py b/starknet_devnet/blueprints/rpc/structures/payloads.py
new file mode 100644
index 000000000..bc73daf80
--- /dev/null
+++ b/starknet_devnet/blueprints/rpc/structures/payloads.py
@@ -0,0 +1,390 @@
+"""
+RPC payload structures
+"""
+
+from __future__ import annotations
+
+from typing import Callable, Union, List, Optional
+
+from starkware.starknet.definitions.general_config import StarknetGeneralConfig
+from starkware.starknet.services.api.contract_class import ContractClass
+from starkware.starknet.services.api.feeder_gateway.response_objects import (
+    StarknetBlock,
+    InvokeSpecificInfo,
+    DeploySpecificInfo,
+    TransactionSpecificInfo,
+    TransactionType,
+    BlockStateUpdate,
+    DeclareSpecificInfo
+)
+from starkware.starknet.services.api.gateway.transaction import InvokeFunction
+from starkware.starknet.services.api.gateway.transaction_utils import compress_program
+from typing_extensions import TypedDict
+
+from starknet_devnet.blueprints.rpc.utils import rpc_root, rpc_felt
+from starknet_devnet.blueprints.rpc.structures.types import RpcBlockStatus, BlockHash, BlockNumber, Felt, \
+    rpc_block_status, TxnHash, Address, NumAsHex, TxnType, rpc_txn_type
+from starknet_devnet.state import state
+
+
+class RpcBlock(TypedDict):
+    """
+    TypeDict for rpc block
+    """
+    status: RpcBlockStatus
+    block_hash: BlockHash
+    parent_hash: BlockHash
+    block_number: BlockNumber
+    new_root: Felt
+    timestamp: int
+    sequencer_address: Felt
+    transactions: Union[List[str], List[RpcTransaction]]
+
+
+async def rpc_block(block: StarknetBlock, tx_type: Optional[str] = "TXN_HASH") -> RpcBlock:
+    """
+    Convert gateway block to rpc block
+    """
+    async def transactions() -> List[RpcTransaction]:
+        # pylint: disable=no-member
+        return [rpc_transaction(tx) for tx in block.transactions]
+
+    async def transaction_hashes() -> List[str]:
+        return [tx["transaction_hash"] for tx in await transactions()]
+
+    def new_root() -> Felt:
+        # pylint: disable=no-member
+        return rpc_root(block.state_root.hex())
+
+    def config() -> StarknetGeneralConfig:
+        devnet_state = state.starknet_wrapper.get_state()
+        _config = devnet_state.general_config
+        return _config
+
+    mapping: dict[str, Callable] = {
+        "TXN_HASH": transaction_hashes,
+        "FULL_TXNS": transactions,
+    }
+    transactions: list = await mapping[tx_type]()
+
+    block: RpcBlock = {
+        "status": rpc_block_status(block.status.name),
+        "block_hash": rpc_felt(block.block_hash),
+        "parent_hash": rpc_felt(block.parent_block_hash or 0),
+        "block_number": block.block_number if block.block_number is not None else 0,
+        "new_root": new_root(),
+        "timestamp": block.timestamp,
+        "sequencer_address": rpc_felt(config().sequencer_address),
+        "transactions": transactions,
+    }
+    return block
+
+
+class RpcInvokeTransaction(TypedDict):
+    """
+    TypedDict for rpc invoke transaction
+    """
+    contract_address: Address
+    entry_point_selector: Felt
+    calldata: List[Felt]
+    # Common
+    transaction_hash: TxnHash
+    max_fee: Felt
+    version: NumAsHex
+    signature: List[Felt]
+    nonce: Felt
+    type: TxnType
+
+
+class RpcDeclareTransaction(TypedDict):
+    """
+    TypedDict for rpc declare transaction
+    """
+    class_hash: Felt
+    sender_address: Address
+    # Common
+    transaction_hash: TxnHash
+    max_fee: Felt
+    version: NumAsHex
+    signature: List[Felt]
+    nonce: Felt
+    type: TxnType
+
+
+class RpcDeployTransaction(TypedDict):
+    """
+    TypedDict for rpc deploy transaction
+    """
+    transaction_hash: TxnHash
+    class_hash: Felt
+    version: NumAsHex
+    type: TxnType
+    contract_address: Felt
+    contract_address_salt: Felt
+    constructor_calldata: List[Felt]
+
+
+RpcTransaction = Union[RpcInvokeTransaction, RpcDeclareTransaction, RpcDeployTransaction]
+
+
+def rpc_transaction(transaction: TransactionSpecificInfo) -> RpcTransaction:
+    """
+    Convert gateway transaction to rpc transaction
+    """
+    tx_mapping = {
+        TransactionType.DEPLOY: rpc_deploy_transaction,
+        TransactionType.INVOKE_FUNCTION: rpc_invoke_transaction,
+        TransactionType.DECLARE: rpc_declare_transaction,
+    }
+    return tx_mapping[transaction.tx_type](transaction)
+
+
+class FunctionCall(TypedDict):
+    """
+    TypedDict for rpc function call
+    """
+    contract_address: Address
+    entry_point_selector: Felt
+    calldata: List[Felt]
+
+
+def rpc_invoke_transaction(transaction: InvokeSpecificInfo) -> RpcInvokeTransaction:
+    """
+    Convert gateway invoke transaction to rpc format
+    """
+    txn: RpcInvokeTransaction = {
+        "contract_address": rpc_felt(transaction.contract_address),
+        "entry_point_selector": rpc_felt(transaction.entry_point_selector),
+        "calldata": [rpc_felt(data) for data in transaction.calldata],
+        "transaction_hash": rpc_felt(transaction.transaction_hash),
+        "max_fee": rpc_felt(transaction.max_fee),
+        "version": hex(0x0),
+        "signature": [rpc_felt(value) for value in transaction.signature],
+        "nonce": rpc_felt(0),
+        "type": rpc_txn_type(transaction.tx_type.name),
+    }
+    return txn
+
+
+def rpc_declare_transaction(transaction: DeclareSpecificInfo) -> RpcDeclareTransaction:
+    """
+    Convert gateway declare transaction to rpc format
+    """
+    txn: RpcDeclareTransaction = {
+        "class_hash": rpc_felt(transaction.class_hash),
+        "sender_address": rpc_felt(transaction.sender_address),
+        "transaction_hash": rpc_felt(transaction.transaction_hash),
+        "max_fee": rpc_felt(transaction.max_fee),
+        "version": hex(transaction.version),
+        "signature": [rpc_felt(value) for value in transaction.signature],
+        "nonce": rpc_felt(transaction.nonce),
+        "type": rpc_txn_type(transaction.tx_type.name),
+    }
+    return txn
+
+
+def rpc_deploy_transaction(transaction: DeploySpecificInfo) -> RpcDeployTransaction:
+    """
+    Convert gateway deploy transaction to rpc format
+    """
+    txn: RpcDeployTransaction = {
+        "transaction_hash": rpc_felt(transaction.transaction_hash),
+        "class_hash": rpc_felt(transaction.class_hash),
+        "version": hex(0x0),
+        "type": rpc_txn_type(transaction.tx_type.name),
+        "contract_address": rpc_felt(transaction.contract_address),
+        "contract_address_salt": rpc_felt(transaction.contract_address_salt),
+        "constructor_calldata": [rpc_felt(data) for data in transaction.constructor_calldata],
+    }
+    return txn
+
+
+class RpcFeeEstimate(TypedDict):
+    """
+    Fee estimate TypedDict for rpc
+    """
+    gas_consumed: NumAsHex
+    gas_price: NumAsHex
+    overall_fee: NumAsHex
+
+
+def rpc_fee_estimate(fee_estimate: dict) -> dict:
+    """
+    Convert gateway estimate_fee response to rpc_fee_estimate
+    """
+    result: RpcFeeEstimate = {
+        "gas_consumed": hex(fee_estimate["gas_usage"]),
+        "gas_price": hex(fee_estimate["gas_price"]),
+        "overall_fee": hex(fee_estimate["overall_fee"]),
+    }
+    return result
+
+
+def make_invoke_function(request_body: dict) -> InvokeFunction:
+    """
+    Convert RPC request to internal InvokeFunction
+    """
+    return InvokeFunction(
+        contract_address=int(request_body["contract_address"], 16),
+        entry_point_selector=int(request_body["entry_point_selector"], 16),
+        calldata=[int(data, 16) for data in request_body["calldata"]],
+        max_fee=int(request_body["max_fee"], 16) if "max_fee" in request_body else 0,
+        version=int(request_body["version"], 16) if "version" in request_body else 0,
+        signature=[int(data, 16) for data in request_body.get("signature", [])],
+    )
+
+
+class EntryPoint(TypedDict):
+    """
+    TypedDict for rpc contract class entry point
+    """
+    offset: NumAsHex
+    selector: Felt
+
+
+class EntryPoints(TypedDict):
+    """
+    TypedDict for rpc contract class entry points
+    """
+    CONSTRUCTOR: List[EntryPoint]
+    EXTERNAL: List[EntryPoint]
+    L1_HANDLER: List[EntryPoint]
+
+
+class RpcContractClass(TypedDict):
+    """
+    TypedDict for rpc contract class
+    """
+    program: str
+    entry_points_by_type: EntryPoints
+
+
+def rpc_contract_class(contract_class: ContractClass) -> RpcContractClass:
+    """
+    Convert gateway contract class to rpc contract class
+    """
+    def program() -> str:
+        _program = contract_class.program.Schema().dump(contract_class.program)
+        return compress_program(_program)
+
+    def entry_points_by_type() -> EntryPoints:
+        _entry_points: EntryPoints = {
+            "CONSTRUCTOR": [],
+            "EXTERNAL": [],
+            "L1_HANDLER": [],
+        }
+        for typ, entry_points in contract_class.entry_points_by_type.items():
+            for entry_point in entry_points:
+                _entry_point: EntryPoint = {
+                    "selector": rpc_felt(entry_point.selector),
+                    "offset": hex(entry_point.offset)
+                }
+                _entry_points[typ.name].append(_entry_point)
+        return _entry_points
+
+    _contract_class: RpcContractClass = {
+        "program": program(),
+        "entry_points_by_type": entry_points_by_type()
+    }
+    return _contract_class
+
+
+class RpcStorageDiff(TypedDict):
+    """
+    TypedDict for rpc storage diff
+    """
+    address: Felt
+    key: Felt
+    value: Felt
+
+
+class RpcDeclaredContractDiff(TypedDict):
+    """
+    TypedDict for rpc declared contract diff
+    """
+    class_hash: Felt
+
+
+class RpcDeployedContractDiff(TypedDict):
+    """
+    TypedDict for rpc deployed contract diff
+    """
+    address: Felt
+    class_hash: Felt
+
+
+class RpcNonceDiff(TypedDict):
+    """
+    TypedDict for rpc nonce diff
+    """
+    contract_address: Address
+    nonce: Felt
+
+
+class RpcStateDiff(TypedDict):
+    """
+    TypedDict for rpc state diff
+    """
+    storage_diffs: List[RpcStorageDiff]
+    declared_contracts: List[RpcDeclaredContractDiff]
+    deployed_contracts: List[RpcDeployedContractDiff]
+    nonces: List[RpcNonceDiff]
+
+
+class RpcStateUpdate(TypedDict):
+    """
+    TypedDict for rpc state update
+    """
+    block_hash: BlockHash
+    new_root: Felt
+    old_root: Felt
+    state_diff: RpcStateDiff
+
+
+def rpc_state_update(state_update: BlockStateUpdate) -> RpcStateUpdate:
+    """
+    Convert gateway state update to rpc state update
+    """
+    def storage_diffs() -> List[RpcStorageDiff]:
+        _storage_diffs = []
+        for address, diffs in state_update.state_diff.storage_diffs.items():
+            for diff in diffs:
+                _diff: RpcStorageDiff = {
+                    "address": rpc_felt(address),
+                    "key": rpc_felt(diff.key),
+                    "value": rpc_felt(diff.value),
+                }
+                _storage_diffs.append(_diff)
+        return _storage_diffs
+
+    def declared_contracts() -> List[RpcDeclaredContractDiff]:
+        _contracts = []
+        for contract in state_update.state_diff.declared_contracts:
+            diff: RpcDeclaredContractDiff = {
+                "class_hash": rpc_felt(contract)
+            }
+            _contracts.append(diff)
+        return _contracts
+
+    def deployed_contracts() -> List[RpcDeployedContractDiff]:
+        _contracts = []
+        for contract in state_update.state_diff.deployed_contracts:
+            diff: RpcDeployedContractDiff = {
+                "address": rpc_felt(contract.address),
+                "class_hash": rpc_felt(contract.class_hash)
+            }
+            _contracts.append(diff)
+        return _contracts
+
+    rpc_state: RpcStateUpdate = {
+        "block_hash": rpc_felt(state_update.block_hash),
+        "new_root": rpc_root(state_update.new_root.hex()),
+        "old_root": rpc_root(state_update.old_root.hex()),
+        "state_diff": {
+            "storage_diffs": storage_diffs(),
+            "declared_contracts": declared_contracts(),
+            "deployed_contracts": deployed_contracts(),
+            "nonces": [],
+        }
+    }
+    return rpc_state
diff --git a/starknet_devnet/blueprints/rpc/structures/responses.py b/starknet_devnet/blueprints/rpc/structures/responses.py
new file mode 100644
index 000000000..1d6c68468
--- /dev/null
+++ b/starknet_devnet/blueprints/rpc/structures/responses.py
@@ -0,0 +1,220 @@
+"""
+RPC response structures
+"""
+
+from typing import List, Optional, Union
+from typing_extensions import TypedDict
+
+from starkware.starknet.definitions.transaction_type import TransactionType
+from starkware.starknet.services.api.feeder_gateway.response_objects import TransactionReceipt, TransactionStatus
+
+from starknet_devnet.blueprints.rpc.utils import rpc_felt
+from starknet_devnet.blueprints.rpc.structures.types import TxnHash, Felt, Address, BlockNumber, BlockHash, TxnStatus
+from starknet_devnet.state import state
+
+
+class RpcInvokeTransactionResult(TypedDict):
+    """
+    TypedDict for rpc invoke transaction result
+    """
+    transaction_hash: TxnHash
+
+
+class RpcDeclareTransactionResult(TypedDict):
+    """
+    TypedDict for rpc declare transaction result
+    """
+    transaction_hash: TxnHash
+    class_hash: Felt
+
+
+class RpcDeployTransactionResult(TypedDict):
+    """
+    TypedDict for rpc deploy transaction result
+    """
+    transaction_hash: TxnHash
+    contract_address: Felt
+
+
+class MessageToL1(TypedDict):
+    """
+    TypedDict for rpc message from l2 to l1
+    """
+    to_address: Felt
+    payload: List[Felt]
+
+
+class MessageToL2(TypedDict):
+    """
+    TypedDict for rpc message from l1 to l2
+    """
+    from_address: str
+    payload: List[Felt]
+
+
+class Event(TypedDict):
+    """
+    TypedDict for rpc event
+    """
+    from_address: Address
+    keys: List[Felt]
+    data: List[Felt]
+
+
+class RpcBaseTransactionReceipt(TypedDict):
+    """
+    TypedDict for rpc transaction receipt
+    """
+    # Common
+    transaction_hash: TxnHash
+    actual_fee: Felt
+    status: TxnStatus
+    status_data: Optional[str]
+    block_hash: BlockHash
+    block_number: BlockNumber
+
+
+class RpcInvokeReceipt(TypedDict):
+    """
+    TypedDict for rpc invoke transaction receipt
+    """
+    messages_sent: List[MessageToL1]
+    l1_origin_message: Optional[MessageToL2]
+    events: List[Event]
+    # Common
+    transaction_hash: TxnHash
+    actual_fee: Felt
+    status: TxnStatus
+    status_data: Optional[str]
+    block_hash: BlockHash
+    block_number: BlockNumber
+
+
+class RpcDeclareReceipt(TypedDict):
+    """
+    TypedDict for rpc declare transaction receipt
+    """
+    # Common
+    transaction_hash: TxnHash
+    actual_fee: Felt
+    status: TxnStatus
+    status_data: Optional[str]
+    block_hash: BlockHash
+    block_number: BlockNumber
+
+
+class RpcDeployReceipt(TypedDict):
+    """
+    TypedDict for rpc declare transaction receipt
+    """
+    # Common
+    transaction_hash: TxnHash
+    actual_fee: Felt
+    status: TxnStatus
+    status_data: Optional[str]
+    block_hash: BlockHash
+    block_number: BlockNumber
+
+
+def rpc_invoke_receipt(txr: TransactionReceipt) -> RpcInvokeReceipt:
+    """
+    Convert rpc invoke transaction receipt to rpc format
+    """
+    def l2_to_l1_messages() -> List[MessageToL1]:
+        return [{
+            "to_address": rpc_felt(message.to_address),
+            "payload": [rpc_felt(p) for p in message.payload]
+        } for message in txr.l2_to_l1_messages]
+
+    def l1_to_l2_message() -> Optional[MessageToL2]:
+        if txr.l1_to_l2_consumed_message is None:
+            return None
+
+        msg: MessageToL2 = {
+            "from_address": txr.l1_to_l2_consumed_message.from_address,
+            "payload": [rpc_felt(p) for p in txr.l1_to_l2_consumed_message.payload]
+        }
+        return msg
+
+    def events() -> List[Event]:
+        _events = []
+        for event in txr.events:
+            event: Event = {
+                "from_address": rpc_felt(event.from_address),
+                "keys": [rpc_felt(e) for e in event.keys],
+                "data": [rpc_felt(d) for d in event.data],
+            }
+            _events.append(event)
+        return _events
+
+    base_receipt = rpc_base_transaction_receipt(txr)
+    receipt: RpcInvokeReceipt = {
+        "messages_sent": l2_to_l1_messages(),
+        "l1_origin_message": l1_to_l2_message(),
+        "events": events(),
+        **base_receipt,
+    }
+    return receipt
+
+
+def rpc_declare_receipt(txr) -> RpcDeclareReceipt:
+    """
+    Convert rpc declare transaction receipt to rpc format
+    """
+    return rpc_base_transaction_receipt(txr)
+
+
+def rpc_deploy_receipt(txr) -> RpcDeployReceipt:
+    """
+    Convert rpc deploy transaction receipt to rpc format
+    """
+    return rpc_base_transaction_receipt(txr)
+
+
+def rpc_base_transaction_receipt(txr: TransactionReceipt) -> RpcBaseTransactionReceipt:
+    """
+    Convert gateway transaction receipt to rpc transaction receipt
+    """
+    def status() -> str:
+        if txr.status is None:
+            return "UNKNOWN"
+
+        mapping = {
+            TransactionStatus.NOT_RECEIVED: "UNKNOWN",
+            TransactionStatus.ACCEPTED_ON_L2: "ACCEPTED_ON_L2",
+            TransactionStatus.ACCEPTED_ON_L1: "ACCEPTED_ON_L1",
+            TransactionStatus.RECEIVED: "RECEIVED",
+            TransactionStatus.PENDING: "PENDING",
+            TransactionStatus.REJECTED: "REJECTED",
+        }
+        return mapping[txr.status]
+
+    def status_data() -> Union[str, None]:
+        if txr.transaction_failure_reason is not None:
+            if txr.transaction_failure_reason.error_message is not None:
+                return txr.transaction_failure_reason.error_message
+        return None
+
+    receipt: RpcBaseTransactionReceipt = {
+        "transaction_hash": rpc_felt(txr.transaction_hash),
+        "actual_fee": rpc_felt(txr.actual_fee or 0),
+        "status": status(),
+        "status_data": status_data(),
+        "block_hash": rpc_felt(txr.block_hash) if txr.block_hash is not None else None,
+        "block_number": txr.block_number,
+    }
+    return receipt
+
+
+def rpc_transaction_receipt(txr: TransactionReceipt) -> dict:
+    """
+    Convert gateway transaction receipt to rpc format
+    """
+    tx_mapping = {
+        TransactionType.DEPLOY: rpc_deploy_receipt,
+        TransactionType.INVOKE_FUNCTION: rpc_invoke_receipt,
+        TransactionType.DECLARE: rpc_declare_receipt,
+    }
+    transaction = state.starknet_wrapper.transactions.get_transaction(hex(txr.transaction_hash)).transaction
+    tx_type = transaction.tx_type
+    return tx_mapping[tx_type](txr)
diff --git a/starknet_devnet/blueprints/rpc/structures/types.py b/starknet_devnet/blueprints/rpc/structures/types.py
new file mode 100644
index 000000000..022ee722f
--- /dev/null
+++ b/starknet_devnet/blueprints/rpc/structures/types.py
@@ -0,0 +1,84 @@
+"""
+RPC types
+"""
+
+from typing import Union
+from typing_extensions import Literal, TypedDict
+
+from starkware.starknet.services.api.feeder_gateway.response_objects import BlockStatus
+
+
+Felt = str
+
+BlockHash = Felt
+BlockNumber = int
+BlockTag = Literal["latest", "pending"]
+
+
+class BlockHashDict(TypedDict):
+    """
+    TypedDict class for BlockId with block hash
+    """
+    block_hash: BlockHash
+
+
+class BlockNumberDict(TypedDict):
+    """
+    TypedDict class for BlockId with block number
+    """
+    block_number: BlockNumber
+
+
+BlockId = Union[BlockHashDict, BlockNumberDict, BlockTag]
+
+TxnStatus = Literal["PENDING", "ACCEPTED_ON_L2", "ACCEPTED_ON_L1", "REJECTED"]
+
+RpcBlockStatus = Literal["PENDING", "ACCEPTED_ON_L2", "ACCEPTED_ON_L1", "REJECTED"]
+
+
+def rpc_block_status(block_status: BlockStatus) -> RpcBlockStatus:
+    """
+    Convert gateway BlockStatus to RpcBlockStatus
+    """
+    block_status_map = {
+        "PENDING": "PENDING",
+        "ABORTED": "REJECTED",
+        "REVERTED": "REJECTED",
+        "ACCEPTED_ON_L2": "ACCEPTED_ON_L2",
+        "ACCEPTED_ON_L1": "ACCEPTED_ON_L1"
+    }
+    return block_status_map[block_status]
+
+
+TxnHash = Felt
+Address = Felt
+NumAsHex = str
+
+# Pending transactions will not be supported since it
+# doesn't make much sense with the current implementation of devnet
+TxnType = Literal["DECLARE", "DEPLOY", "INVOKE"]
+
+
+def rpc_txn_type(transaction_type: str) -> TxnType:
+    """
+    Convert gateway transaction type to RPC TxnType
+    """
+    txn_type_map = {
+        "DEPLOY": "DEPLOY",
+        "DECLARE": "DECLARE",
+        "INVOKE_FUNCTION": "INVOKE",
+    }
+    if transaction_type not in txn_type_map:
+        raise RpcError(code=-1, message=f"Current implementation does not support {transaction_type} transaction type")
+    return txn_type_map[transaction_type]
+
+
+class RpcError(Exception):
+    """
+    Error message returned by rpc
+    """
+
+    def __init__(self, code, message):
+        super().__init__(message)
+        self.code = code
+        self.message = message
diff --git a/starknet_devnet/blueprints/rpc/transactions.py b/starknet_devnet/blueprints/rpc/transactions.py
new file mode 100644
index 000000000..89ab9400a
--- /dev/null
+++ b/starknet_devnet/blueprints/rpc/transactions.py
@@ -0,0 +1,175 @@
+"""
+RPC transaction endpoints
+"""
+
+import dataclasses
+from typing import List, Optional
+
+from marshmallow.exceptions import MarshmallowError
+from starkware.starknet.definitions import constants
+from starkware.starknet.services.api.contract_class import ContractClass
+from starkware.starknet.services.api.feeder_gateway.response_objects import TransactionStatus
+from starkware.starknet.services.api.gateway.transaction import InvokeFunction, Declare, DECLARE_SENDER_ADDRESS, Deploy
+from starkware.starknet.services.api.gateway.transaction_utils import decompress_program
+from starkware.starkware_utils.error_handling import StarkException
+
+from starknet_devnet.blueprints.rpc.utils import get_block_by_block_id, rpc_felt, \
+    assert_block_id_is_latest
+from starknet_devnet.blueprints.rpc.structures.payloads import rpc_transaction, RpcTransaction, FunctionCall, \
+    RpcContractClass, RpcInvokeTransaction, make_invoke_function, rpc_fee_estimate
+from starknet_devnet.blueprints.rpc.structures.responses import rpc_transaction_receipt, RpcInvokeTransactionResult, \
+    RpcDeclareTransactionResult, RpcDeployTransactionResult
+from starknet_devnet.blueprints.rpc.structures.types import TxnHash, BlockId, NumAsHex, Felt, RpcError
+from starknet_devnet.state import state
+from starknet_devnet.util import StarknetDevnetException
+
+
+async def get_transaction_by_hash(transaction_hash: TxnHash) -> dict:
+    """
+    Get the details and status of a submitted transaction
+    """
+    try:
+        result = state.starknet_wrapper.transactions.get_transaction(transaction_hash)
+    except StarknetDevnetException as ex:
+        raise RpcError(code=25, message="Invalid transaction hash") from ex
+
+    if result.status == TransactionStatus.NOT_RECEIVED:
+        raise RpcError(code=25, message="Invalid transaction hash")
+
+    return rpc_transaction(result.transaction)
+
+
+async def get_transaction_by_block_id_and_index(block_id: BlockId, index: int) -> dict:
+    """
+    Get the details of a transaction by a given block id and index
+    """
+    block = get_block_by_block_id(block_id)
+
+    try:
+        transaction_hash: int = block.transactions[index].transaction_hash
+    except IndexError as ex:
+        raise RpcError(code=27, message="Invalid transaction index in a block") from ex
+
+    return await get_transaction_by_hash(transaction_hash=rpc_felt(transaction_hash))
+
+
+async def get_transaction_receipt(transaction_hash: TxnHash) -> dict:
+    """
+    Get the transaction receipt by the transaction hash
+    """
+    try:
+        result = state.starknet_wrapper.transactions.get_transaction_receipt(tx_hash=transaction_hash)
+    except StarknetDevnetException as ex:
+        raise RpcError(code=25, message="Invalid transaction hash") from ex
+
+    if result.status == TransactionStatus.NOT_RECEIVED:
+        raise RpcError(code=25, message="Invalid transaction hash")
+
+    return rpc_transaction_receipt(result)
+
+
+async def pending_transactions() -> List[RpcTransaction]:
+    """
+    Returns the transactions in the transaction pool, recognized by this sequencer
+    """
+    raise NotImplementedError()
+
+
+async def add_invoke_transaction(function_invocation: FunctionCall, max_fee: NumAsHex, version: NumAsHex,
+                                 signature: Optional[List[Felt]] = None) -> dict:
+    """
+    Submit a new transaction to be added to the chain
+    """
+    invoke_function = InvokeFunction(
+        contract_address=int(function_invocation["contract_address"], 16),
+        entry_point_selector=int(function_invocation["entry_point_selector"], 16),
+        calldata=[int(data, 16) for data in function_invocation["calldata"]],
+        max_fee=int(max_fee, 16),
+        version=int(version, 16),
+        signature=[int(data, 16) for data in signature] if signature is not None else [],
+    )
+
+    _, transaction_hash, _ = await state.starknet_wrapper.invoke(invoke_function=invoke_function)
+    return RpcInvokeTransactionResult(
+        transaction_hash=rpc_felt(transaction_hash),
+    )
+
+
+async def add_declare_transaction(contract_class: RpcContractClass, version: NumAsHex) -> dict:
+    """
+    Submit a new class declaration transaction
+    """
+    try:
+        decompressed_program = decompress_program({"contract_class": contract_class}, False)
+        decompressed_program = decompressed_program["contract_class"]
+
+        contract_definition = ContractClass.load(decompressed_program)
+        # Replace None with [] in abi key to avoid Missing Abi exception
+        contract_definition = dataclasses.replace(contract_definition, abi=[])
+    except (StarkException, TypeError, MarshmallowError) as ex:
+        raise RpcError(code=50, message="Invalid contract class") from ex
+
+    declare_transaction = Declare(
+        contract_class=contract_definition,
+        version=int(version, 16),
+        sender_address=DECLARE_SENDER_ADDRESS,
+        max_fee=0,
+        signature=[],
+        nonce=0,
+    )
+
+    class_hash, transaction_hash = await state.starknet_wrapper.declare(declare_transaction=declare_transaction)
+    return RpcDeclareTransactionResult(
+        transaction_hash=rpc_felt(transaction_hash),
+        class_hash=rpc_felt(class_hash),
+    )
+
+
+async def add_deploy_transaction(contract_address_salt: Felt, constructor_calldata: List[Felt],
+                                 contract_definition: RpcContractClass) -> dict:
+    """
+    Submit a new deploy contract transaction
+    """
+    try:
+        decompressed_program = decompress_program({"contract_definition": contract_definition}, False)
+        decompressed_program = decompressed_program["contract_definition"]
+
+        contract_class = ContractClass.load(decompressed_program)
+        contract_class = dataclasses.replace(contract_class, abi=[])
+    except (StarkException, TypeError, MarshmallowError) as ex:
+        raise RpcError(code=50, message="Invalid contract class") from ex
+
+    deploy_transaction = Deploy(
+        contract_address_salt=int(contract_address_salt, 16),
+        constructor_calldata=[int(data, 16) for data in constructor_calldata],
+        contract_definition=contract_class,
+        version=constants.TRANSACTION_VERSION,
+    )
+
+    contract_address, transaction_hash = await state.starknet_wrapper.deploy(deploy_transaction=deploy_transaction)
+    return RpcDeployTransactionResult(
+        transaction_hash=rpc_felt(transaction_hash),
+        contract_address=rpc_felt(contract_address),
+    )
+
+
+async def estimate_fee(request: RpcInvokeTransaction, block_id: BlockId) -> dict:
+    """
+    Estimate the fee for a given StarkNet transaction
+    """
+    assert_block_id_is_latest(block_id)
+
+    if not state.starknet_wrapper.contracts.is_deployed(int(request["contract_address"], 16)):
+        raise RpcError(code=20, message="Contract not found")
+
+    invoke_function = make_invoke_function(request)
+
+    try:
+        fee_response = await state.starknet_wrapper.calculate_actual_fee(invoke_function)
+    except StarkException as ex:
+        if f"Entry point {hex(int(request['entry_point_selector'], 16))} not found" in ex.message:
+            raise RpcError(code=21, message="Invalid message selector") from ex
+        if "While handling calldata" in ex.message:
+            raise RpcError(code=22, message="Invalid call data") from ex
+        raise RpcError(code=-1, message=ex.message) from ex
+    return rpc_fee_estimate(fee_response)
diff --git a/starknet_devnet/blueprints/rpc/utils.py b/starknet_devnet/blueprints/rpc/utils.py
new file mode 100644
index 000000000..ca4f5074a
--- /dev/null
+++ b/starknet_devnet/blueprints/rpc/utils.py
@@ -0,0 +1,96 @@
+"""
+RPC utilities
+"""
+
+from starknet_devnet.blueprints.rpc.structures.types import BlockId, RpcError, Felt
+from starknet_devnet.util import StarknetDevnetException
+from starknet_devnet.state import state
+
+
+def block_tag_to_block_number(block_id: BlockId) -> BlockId:
+    """
+    Changes block_id from tag to dict with "block_number" field
+    """
+    if isinstance(block_id, str):
+        if block_id == "latest":
+            return {"block_number": state.starknet_wrapper.blocks.get_number_of_blocks() - 1}
+
+        if block_id == "pending":
+            raise RpcError(code=-1, message="Calls with block_id == 'pending' are not supported currently.")
+
+        raise RpcError(code=24, message="Invalid block id")
+
+    return block_id
+
+
+def get_block_by_block_id(block_id: BlockId) -> dict:
+    """
+    Get block using different method depending on block_id type
+    """
+    if block_id in ["latest", "pending"]:
+        block_id = block_tag_to_block_number(block_id)
+
+    try:
+        if "block_hash" in block_id:
+            return state.starknet_wrapper.blocks.get_by_hash(block_hash=block_id["block_hash"])
+        return state.starknet_wrapper.blocks.get_by_number(block_number=block_id["block_number"])
+    except StarknetDevnetException as ex:
+        raise RpcError(code=24, message="Invalid block id") from ex
+
+
+def assert_block_id_is_latest(block_id: BlockId) -> None:
+    """
+    Assert block_id is "latest" and throw RpcError otherwise
+    """
+    if block_id != "latest":
+        raise RpcError(code=-1, message="Calls with block_id != 'latest' are not supported currently.")
+
+
+def rpc_felt(value: int) -> Felt:
+    """
+    Convert integer to 0x0 prefixed felt
+    """
+    if value == 0:
+        return "0x00"
+    return "0x0" + hex(value).lstrip("0x")
+
+
+def pad_zero(felt: str) -> Felt:
+    """
+    Convert felt with format `0xValue` to format `0x0Value`
+    """
+    if felt == "0x0":
+        return "0x00"
+    return "0x0" + felt.lstrip("0x")
+
+
+def rpc_root(root: str) -> Felt:
+    """
+    Convert 0 prefixed root to 0x prefixed root
+    """
+    return "0x0" + root.lstrip("0")
+
+
+def rpc_response(message_id: int, content: dict) -> dict:
+    """
+    Wrap response content in rpc format
+    """
+    return {
+        "jsonrpc": "2.0",
+        "id": message_id,
+        "result": content
+    }
+
+
+def rpc_error(message_id: int, code: int, message: str) -> dict:
+    """
+    Wrap error in rpc format
+    """
+    return {
+        "jsonrpc": "2.0",
+        "id": message_id,
+        "error": {
+            "code": code,
+            "message": message
+        }
+    }
diff --git a/starknet_devnet/server.py b/starknet_devnet/server.py
index c70020730..45779af7d 100644
--- a/starknet_devnet/server.py
+++ b/starknet_devnet/server.py
@@ -18,7 +18,7 @@
 from .blueprints.gateway import gateway
 from .blueprints.feeder_gateway import feeder_gateway
 from .blueprints.postman import postman
-from .blueprints.rpc import rpc
+from .blueprints.rpc.routes import rpc
 from .state import state
 from .devnet_config import DevnetConfig, DumpOn, parse_args
 
diff --git a/starknet_devnet/starknet_wrapper.py b/starknet_devnet/starknet_wrapper.py
index 0adadd8c5..3017170da 100644
--- a/starknet_devnet/starknet_wrapper.py
+++ b/starknet_devnet/starknet_wrapper.py
@@ -23,6 +23,7 @@
 from starkware.starknet.testing.objects import FunctionInvocation
 
 from .accounts import Accounts
+from .blueprints.rpc.structures.types import Felt
 from .fee_token import FeeToken
 from .general_config import DEFAULT_GENERAL_CONFIG
 from .origin import NullOrigin, Origin
@@ -363,7 +364,7 @@ async def __register_new_contracts(self, internal_calls: List[Union[FunctionInvo
                 self.store_contract(internal_call.contract_address, contract, contract_class, tx_hash)
             await self.__register_new_contracts(internal_call.internal_calls, tx_hash)
 
-    async def get_storage_at(self, contract_address: int, key: int) -> str:
+    async def get_storage_at(self, contract_address: int, key: int) -> Felt:
         """
         Returns the storage identified by `key`
         from the contract at `contract_address`.


Test patch:
diff --git a/test/rpc/conftest.py b/test/rpc/conftest.py
index 41b3d7bf3..66fc0e630 100644
--- a/test/rpc/conftest.py
+++ b/test/rpc/conftest.py
@@ -7,31 +7,21 @@
 import json
 import typing
 
-from test.util import load_file_content
-from test.test_endpoints import send_transaction
+from test.util import load_file_content, run_devnet_in_background, terminate_and_wait
 
-import pytest
+from starkware.starknet.definitions import constants
 from starkware.starknet.services.api.contract_class import ContractClass
 from starkware.starknet.services.api.gateway.transaction import Transaction, Deploy
 
-from .rpc_utils import gateway_call, restart
-
+import pytest
+from starknet_devnet.blueprints.rpc.structures.types import BlockNumberDict, BlockHashDict, Felt
+from .rpc_utils import gateway_call, get_block_with_transaction, pad_zero, add_transaction
 
 DEPLOY_CONTENT = load_file_content("deploy_rpc.json")
 INVOKE_CONTENT = load_file_content("invoke_rpc.json")
 DECLARE_CONTENT = load_file_content("declare.json")
 
 
-@pytest.fixture(autouse=True)
-def before_and_after():
-    """Fixture that runs before and after each test"""
-    #before test
-
-    yield
-    #after test
-    restart()
-
-
 @pytest.fixture(name="contract_class")
 def fixture_contract_class() -> ContractClass:
     """
@@ -42,12 +32,12 @@ def fixture_contract_class() -> ContractClass:
 
 
 @pytest.fixture(name="class_hash")
-def fixture_class_hash(deploy_info) -> str:
+def fixture_class_hash(deploy_info) -> Felt:
     """
     Class hash of deployed contract
     """
     class_hash = gateway_call("get_class_hash_at", contractAddress=deploy_info["address"])
-    return class_hash
+    return pad_zero(class_hash)
 
 
 @pytest.fixture(name="deploy_info")
@@ -55,9 +45,7 @@ def fixture_deploy_info() -> dict:
     """
     Deploy a contract on devnet and return deployment info dict
     """
-    resp = send_transaction(json.loads(DEPLOY_CONTENT))
-    deploy_info = json.loads(resp.data.decode("utf-8"))
-    return deploy_info
+    return add_transaction(json.loads(DEPLOY_CONTENT))
 
 
 @pytest.fixture(name="invoke_info")
@@ -67,8 +55,7 @@ def fixture_invoke_info() -> dict:
     """
     invoke_tx = json.loads(INVOKE_CONTENT)
     invoke_tx["calldata"] = ["0"]
-    resp = send_transaction(invoke_tx)
-    invoke_info = json.loads(resp.data.decode("utf-8"))
+    invoke_info = add_transaction(invoke_tx)
     return {**invoke_info, **invoke_tx}
 
 
@@ -78,8 +65,7 @@ def fixture_declare_info() -> dict:
     Make a declare transaction on devnet and return declare info dict
     """
     declare_tx = json.loads(DECLARE_CONTENT)
-    resp = send_transaction(declare_tx)
-    declare_info = json.loads(resp.data.decode("utf-8"))
+    declare_info = add_transaction(declare_tx)
     return {**declare_info, **declare_tx}
 
 
@@ -105,3 +91,53 @@ def fixture_declare_content() -> dict:
     Declare content JSON object
     """
     return json.loads(DECLARE_CONTENT)
+
+
+@pytest.fixture(name="gateway_block")
+def fixture_gateway_block(deploy_info) -> dict:
+    """
+    Block with Deploy transaction
+    """
+    return get_block_with_transaction(deploy_info["transaction_hash"])
+
+
+@pytest.fixture(name="block_id")
+def fixture_block_id(gateway_block, request) -> dict:
+    """
+    BlockId of gateway_block depending on type in request
+    """
+    block_id_map = {
+        "hash": BlockNumberDict(block_number=gateway_block["block_number"]),
+        "number": BlockHashDict(block_hash=pad_zero(gateway_block["block_hash"])),
+        "tag": "latest",
+    }
+    return block_id_map[request.param]
+
+
+@pytest.fixture(name="rpc_invoke_tx_common")
+def fixture_rpc_invoke_tx_common() -> dict:
+    """
+    Common fields on RpcInvokeTransaction
+    """
+    return {
+        # It is not verified and might be removed in next RPC version
+        "transaction_hash": "0x00",
+        "max_fee": "0x00",
+        "version": hex(constants.TRANSACTION_VERSION),
+        "signature": [],
+        "nonce": "0x00",
+        "type": "INVOKE",
+    }
+
+
+@pytest.fixture(name="run_devnet_in_background")
+def fixture_run_devnet_in_background(request) -> None:
+    """
+    Run devnet instance in background
+    """
+    args = getattr(request, "param", [])
+    proc = run_devnet_in_background(*args)
+    try:
+        yield
+    finally:
+        terminate_and_wait(proc)
diff --git a/test/rpc/rpc_utils.py b/test/rpc/rpc_utils.py
index 792fc3f3d..b68914359 100644
--- a/test/rpc/rpc_utils.py
+++ b/test/rpc/rpc_utils.py
@@ -4,45 +4,71 @@
 
 from __future__ import annotations
 
-import json
 from typing import Union
 
-from starknet_devnet.server import app
+import requests
 
-def restart():
-    """Restart app"""
-    resp = app.test_client().post("/restart")
-    assert resp.status_code == 200
+from starknet_devnet.blueprints.rpc.structures.types import Felt
+from ..settings import APP_URL
 
 
-def rpc_call(method: str, params: Union[dict, list]) -> dict:
+class BackgroundDevnetClient:
+    """ A thin wrapper for requests, to interact with a background devnet instance """
+
+    @staticmethod
+    def get(endpoint: str) -> requests.Response:
+        """ Submit get request at given endpoint """
+        return requests.get(f"{APP_URL}{endpoint}")
+
+    @staticmethod
+    def post(endpoint: str, body: dict) -> requests.Response:
+        """ Submit post request with given dict in body (JSON) """
+        return requests.post(f"{APP_URL}{endpoint}", json=body)
+
+
+def make_rpc_payload(method: str, params: Union[dict, list]):
     """
-    Make a call to the RPC endpoint
+    Make a wrapper for rpc call
     """
-    req = {
+    return {
         "jsonrpc": "2.0",
         "method": method,
         "params": params,
         "id": 0
     }
 
-    resp = app.test_client().post(
-        "/rpc",
-        content_type="application/json",
-        data=json.dumps(req)
-    )
-    result = json.loads(resp.data.decode("utf-8"))
-    return result
+
+def rpc_call_background_devnet(method: str, params: Union[dict, list]):
+    """
+    RPC call to devnet in backgound
+    """
+    payload = make_rpc_payload(method, params)
+    return BackgroundDevnetClient.post("/rpc", payload).json()
+
+
+def rpc_call(method: str, params: Union[dict, list]) -> dict:
+    """
+    Make a call to the RPC endpoint
+    """
+    return BackgroundDevnetClient.post("/rpc", body=make_rpc_payload(method, params)).json()
+
+
+def add_transaction(body: dict) -> dict:
+    """
+    Make a call to the gateway add_transaction endpoint
+    """
+    response = BackgroundDevnetClient.post("/gateway/add_transaction", body)
+    return response.json()
 
 
 def gateway_call(method: str, **kwargs):
     """
     Make a call to the gateway
     """
-    resp = app.test_client().get(
+    response = BackgroundDevnetClient.get(
         f"/feeder_gateway/{method}?{'&'.join(f'{key}={value}&' for key, value in kwargs.items())}"
     )
-    return json.loads(resp.data.decode("utf-8"))
+    return response.json()
 
 
 def get_block_with_transaction(transaction_hash: str) -> dict:
@@ -50,14 +76,16 @@ def get_block_with_transaction(transaction_hash: str) -> dict:
     Retrieve block for given transaction
     """
     transaction = gateway_call("get_transaction", transactionHash=transaction_hash)
+    assert transaction["status"] != "NOT_RECEIVED", f"Transaction {transaction_hash} was not received or does not exist"
     block_number: int = transaction["block_number"]
     block = gateway_call("get_block", blockNumber=block_number)
     return block
 
 
-def pad_zero(felt: str) -> str:
+def pad_zero(felt: str) -> Felt:
     """
     Convert felt with format `0xValue` to format `0x0Value`
     """
-    felt = felt.lstrip("0x")
-    return "0x0" + felt
+    if felt == "0x0":
+        return "0x00"
+    return "0x0" + felt.lstrip("0x")
diff --git a/test/rpc/test_rpc_blocks.py b/test/rpc/test_rpc_blocks.py
index 5f592fe77..43a684f58 100644
--- a/test/rpc/test_rpc_blocks.py
+++ b/test/rpc/test_rpc_blocks.py
@@ -1,214 +1,154 @@
 """
 Tests RPC blocks
 """
-
 from test.shared import GENESIS_BLOCK_NUMBER, INCORRECT_GENESIS_BLOCK_HASH
+
+import pytest
+from starknet_devnet.blueprints.rpc.structures.types import BlockNumberDict, BlockHashDict, rpc_txn_type
+from starknet_devnet.blueprints.rpc.utils import rpc_root
 from starknet_devnet.general_config import DEFAULT_GENERAL_CONFIG
 
-from .rpc_utils import rpc_call, get_block_with_transaction, pad_zero, gateway_call
+from .rpc_utils import rpc_call, pad_zero, gateway_call
 
 
-def test_get_block_by_number(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background")
+@pytest.mark.parametrize("block_id", ["hash", "number", "tag"], indirect=True)
+def test_get_block_with_tx_hashes(deploy_info, gateway_block, block_id):
     """
-    Get block by number
+    Get block with tx hashes
     """
-    gateway_block: dict = get_block_with_transaction(
-        deploy_info["transaction_hash"])
     block_hash: str = gateway_block["block_hash"]
     block_number: int = gateway_block["block_number"]
-    new_root: str = gateway_block["state_root"]
+    new_root: str = rpc_root(gateway_block["state_root"])
 
     resp = rpc_call(
-        "starknet_getBlockByNumber", params={"block_number": block_number}
+        "starknet_getBlockWithTxHashes", params={"block_id": block_id}
     )
     block = resp["result"]
     transaction_hash: str = pad_zero(deploy_info["transaction_hash"])
 
-    assert block["block_hash"] == pad_zero(block_hash)
-    assert block["parent_hash"] == pad_zero(gateway_block["parent_block_hash"])
-    assert block["block_number"] == block_number
-    assert block["status"] == "ACCEPTED_ON_L2"
-    assert block["sequencer_address"] == hex(
-        DEFAULT_GENERAL_CONFIG.sequencer_address)
-    assert block["new_root"] == pad_zero(new_root)
-    assert block["transactions"] == [transaction_hash]
+    assert block == {
+        "block_hash": pad_zero(block_hash),
+        "parent_hash": pad_zero(gateway_block["parent_block_hash"]),
+        "block_number": block_number,
+        "status": "ACCEPTED_ON_L2",
+        "sequencer_address": pad_zero(hex(DEFAULT_GENERAL_CONFIG.sequencer_address)),
+        "new_root": pad_zero(new_root),
+        "timestamp": gateway_block["timestamp"],
+        "transactions": [transaction_hash],
+    }
 
 
-# pylint: disable=unused-argument
-def test_get_block_by_number_raises_on_incorrect_number(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background", "deploy_info")
+@pytest.mark.parametrize("block_id", [BlockNumberDict(block_number=1234),
+                                      BlockHashDict(block_hash=pad_zero(INCORRECT_GENESIS_BLOCK_HASH))])
+def test_get_block_with_tx_hashes_raises_on_incorrect_block_id(block_id):
     """
-    Get block by incorrect number
+    Get block with tx hashes by incorrect block_id
     """
     ex = rpc_call(
-        "starknet_getBlockByNumber", params={"block_number": 1234}
+        "starknet_getBlockWithTxHashes", params={"block_id": block_id}
     )
 
     assert ex["error"] == {
-        "code": 26,
-        "message": "Invalid block number"
+        "code": 24,
+        "message": "Invalid block id"
     }
 
 
-def test_get_block_by_hash(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background", "deploy_info")
+@pytest.mark.parametrize("block_id", ["hash", "number", "tag"], indirect=True)
+def test_get_block_with_txs(gateway_block, block_id):
     """
-    Get block by hash
+    Get block with txs by block id
     """
-    gateway_block: dict = get_block_with_transaction(
-        deploy_info["transaction_hash"])
     block_hash: str = gateway_block["block_hash"]
-    new_root: str = gateway_block["state_root"]
-    transaction_hash: str = pad_zero(deploy_info["transaction_hash"])
-
-    resp = rpc_call(
-        "starknet_getBlockByHash", params={"block_hash": block_hash}
-    )
-    block = resp["result"]
-
-    assert block["block_hash"] == pad_zero(block_hash)
-    assert block["parent_hash"] == pad_zero(gateway_block["parent_block_hash"])
-    assert block["block_number"] == gateway_block["block_number"]
-    assert block["status"] == "ACCEPTED_ON_L2"
-    assert block["sequencer_address"] == hex(
-        DEFAULT_GENERAL_CONFIG.sequencer_address)
-    assert block["new_root"] == pad_zero(new_root)
-    assert block["transactions"] == [transaction_hash]
-
-
-def test_get_block_by_hash_full_txn_scope(deploy_info):
-    """
-    Get block by hash with scope FULL_TXNS
-    """
-    block_hash: str = get_block_with_transaction(
-        deploy_info["transaction_hash"])["block_hash"]
-    transaction_hash: str = pad_zero(deploy_info["transaction_hash"])
-    contract_address: str = pad_zero(deploy_info["address"])
-
-    resp = rpc_call(
-        "starknet_getBlockByHash",
-        params={
-            "block_hash": block_hash,
-            "requested_scope": "FULL_TXNS"
-        }
-    )
-    block = resp["result"]
-
-    assert block["transactions"] == [{
-        "txn_hash": transaction_hash,
-        "max_fee": "0x0",
-        "contract_address": contract_address,
-        "calldata": [],
-        "entry_point_selector": None,
-        "signature": [],
-        "version": "0x0"
-    }]
-
-
-def test_get_block_by_hash_full_txn_and_receipts_scope(deploy_info):
-    """
-    Get block by hash with scope FULL_TXN_AND_RECEIPTS
-    """
-    block_hash: str = get_block_with_transaction(
-        deploy_info["transaction_hash"])["block_hash"]
-    transaction_hash: str = pad_zero(deploy_info["transaction_hash"])
-    contract_address: str = pad_zero(deploy_info["address"])
+    block_number: int = gateway_block["block_number"]
+    new_root: str = rpc_root(gateway_block["state_root"])
+    block_tx = gateway_block["transactions"][0]
 
     resp = rpc_call(
-        "starknet_getBlockByHash",
-        params={
-            "block_hash": block_hash,
-            "requested_scope": "FULL_TXN_AND_RECEIPTS"
-        }
+        "starknet_getBlockWithTxs", params={"block_id": block_id}
     )
     block = resp["result"]
 
-    assert block["transactions"] == [{
-        "txn_hash": transaction_hash,
-        "max_fee": "0x0",
-        "contract_address": contract_address,
-        "calldata": [],
-        "entry_point_selector": None,
-        "signature": [],
-        "version": "0x0",
-        "actual_fee": "0x0",
+    assert block == {
+        "block_hash": pad_zero(block_hash),
+        "parent_hash": pad_zero(gateway_block["parent_block_hash"]),
+        "block_number": block_number,
         "status": "ACCEPTED_ON_L2",
-        "statusData": None,
-    }]
-
-
-def test_get_block_by_hash_raises_on_incorrect_hash(deploy_info):
-    """
-    Get block by incorrect hash
-    """
-    ex = rpc_call(
-        "starknet_getBlockByHash", params={"block_hash": INCORRECT_GENESIS_BLOCK_HASH}
-    )
-
-    assert ex["error"] == {
-        "code": 24,
-        "message": "Invalid block hash"
+        "sequencer_address": pad_zero(hex(DEFAULT_GENERAL_CONFIG.sequencer_address)),
+        "new_root": pad_zero(new_root),
+        "timestamp": gateway_block["timestamp"],
+        "transactions": [
+            {
+                "class_hash": pad_zero(block_tx["class_hash"]),
+                "constructor_calldata": [pad_zero(data) for data in block_tx["constructor_calldata"]],
+                "contract_address": pad_zero(block_tx["contract_address"]),
+                "contract_address_salt": pad_zero(block_tx["contract_address_salt"]),
+                "transaction_hash": pad_zero(block_tx["transaction_hash"]),
+                "type": rpc_txn_type(block_tx["type"]),
+                "version": "0x0",
+            }
+        ],
     }
 
 
-def test_get_block_transaction_count_by_hash(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background", "deploy_info")
+@pytest.mark.parametrize("block_id", [BlockNumberDict(block_number=1234),
+                                      BlockHashDict(block_hash=pad_zero(INCORRECT_GENESIS_BLOCK_HASH))])
+def test_get_block_with_txs_raises_on_incorrect_block_id(block_id):
     """
-    Get count of transactions in block by block hash
-    """
-    block = get_block_with_transaction(deploy_info["transaction_hash"])
-    block_hash: str = block["block_hash"]
-
-    resp = rpc_call(
-        "starknet_getBlockTransactionCountByHash", params={"block_hash": block_hash}
-    )
-    count = resp["result"]
-
-    assert count == 1
-
-
-def test_get_block_transaction_count_by_hash_raises_on_incorrect_hash(deploy_info):
-    """
-    Get count of transactions in block by incorrect block hash
+    Get block with txs by incorrect block_id
     """
     ex = rpc_call(
-        "starknet_getBlockTransactionCountByHash", params={"block_hash": INCORRECT_GENESIS_BLOCK_HASH}
+        "starknet_getBlockWithTxHashes", params={"block_id": block_id}
     )
 
     assert ex["error"] == {
         "code": 24,
-        "message": "Invalid block hash"
+        "message": "Invalid block id"
     }
 
 
-def test_get_block_transaction_count_by_number(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background", "deploy_info", "gateway_block")
+@pytest.mark.parametrize("block_id", ["hash", "number", "tag"], indirect=True)
+def test_get_block_transaction_count(block_id):
     """
-    Get count of transactions in block by block number
+    Get count of transactions in block by block id
     """
-    block_number: int = GENESIS_BLOCK_NUMBER + 1
+    if "block_number" in block_id:
+        block_id["block_number"] = GENESIS_BLOCK_NUMBER + 1
 
     resp = rpc_call(
-        "starknet_getBlockTransactionCountByNumber", params={"block_number": block_number}
+        "starknet_getBlockTransactionCount", params={"block_id": block_id}
     )
     count = resp["result"]
 
     assert count == 1
 
 
-def test_get_block_transaction_count_by_number_raises_on_incorrect_number(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background", "deploy_info")
+@pytest.mark.parametrize("block_id", [BlockNumberDict(block_number=99999),
+                                      BlockHashDict(block_hash=pad_zero(INCORRECT_GENESIS_BLOCK_HASH))])
+def test_get_block_transaction_count_raises_on_incorrect_block_id(block_id):
     """
-    Get count of transactions in block by incorrect block number
+    Get count of transactions in block by incorrect block id
     """
     ex = rpc_call(
-        "starknet_getBlockTransactionCountByNumber", params={"block_number": 99999}
+        "starknet_getBlockTransactionCount", params={"block_id": block_id}
     )
 
     assert ex["error"] == {
-        "code": 26,
-        "message": "Invalid block number"
+        "code": 24,
+        "message": "Invalid block id"
     }
 
 
-def test_get_block_number(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background", "deploy_info")
+def test_get_block_number():
     """
-    Get the number of the latest accepted  block
+    Get the number of the latest accepted block
     """
 
     latest_block = gateway_call("get_block", blockNumber="latest")
diff --git a/test/rpc/test_rpc_call.py b/test/rpc/test_rpc_call.py
index 42571bfcb..a011284b8 100644
--- a/test/rpc/test_rpc_call.py
+++ b/test/rpc/test_rpc_call.py
@@ -5,9 +5,10 @@
 import pytest
 from starkware.starknet.public.abi import get_selector_from_name
 
-from .rpc_utils import rpc_call
+from .rpc_utils import rpc_call, pad_zero
 
 
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_call(deploy_info):
     """
     Call contract
@@ -16,30 +17,32 @@ def test_call(deploy_info):
 
     resp = rpc_call(
         "starknet_call", params={
-            "contract_address": contract_address,
-            "entry_point_selector": hex(get_selector_from_name("get_balance")),
-            "calldata": [],
-            "block_hash": "latest"
+            "request": {
+                "contract_address": pad_zero(contract_address),
+                "entry_point_selector": hex(get_selector_from_name("get_balance")),
+                "calldata": [],
+            },
+            "block_id": "latest"
         }
     )
     result = resp["result"]
 
-    assert isinstance(result["result"], list)
-    assert len(result["result"]) == 1
-    assert result["result"][0] == "0x0"
+    assert result["result"] == ["0x00"]
 
 
-# pylint: disable=unused-argument
-def test_call_raises_on_incorrect_contract_address(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background", "deploy_info")
+def test_call_raises_on_incorrect_contract_address():
     """
     Call contract with incorrect address
     """
     ex = rpc_call(
         "starknet_call", params={
-            "contract_address": "0x07b529269b82f3f3ebbb2c463a9e1edaa2c6eea8fa308ff70b30398766a2e20c",
-            "entry_point_selector": hex(get_selector_from_name("get_balance")),
-            "calldata": [],
-            "block_hash": "latest"
+            "request": {
+                "contract_address": "0x07b529269b82f3f3ebbb2c463a9e1edaa2c6eea8fa308ff70b30398766a2e20c",
+                "entry_point_selector": hex(get_selector_from_name("get_balance")),
+                "calldata": [],
+            },
+            "block_id": "latest"
         }
     )
 
@@ -49,6 +52,7 @@ def test_call_raises_on_incorrect_contract_address(deploy_info):
     }
 
 
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_call_raises_on_incorrect_selector(deploy_info):
     """
     Call contract with incorrect entry point selector
@@ -57,10 +61,12 @@ def test_call_raises_on_incorrect_selector(deploy_info):
 
     ex = rpc_call(
         "starknet_call", params={
-            "contract_address": contract_address,
-            "entry_point_selector": hex(get_selector_from_name("xxxxxxx")),
-            "calldata": [],
-            "block_hash": "latest"
+            "request": {
+                "contract_address": pad_zero(contract_address),
+                "entry_point_selector": hex(get_selector_from_name("xxxxxxx")),
+                "calldata": [],
+            },
+            "block_id": "latest"
         }
     )
 
@@ -70,6 +76,7 @@ def test_call_raises_on_incorrect_selector(deploy_info):
     }
 
 
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_call_raises_on_invalid_calldata(deploy_info):
     """
     Call contract with incorrect calldata
@@ -78,10 +85,12 @@ def test_call_raises_on_invalid_calldata(deploy_info):
 
     ex = rpc_call(
         "starknet_call", params={
-            "contract_address": contract_address,
-            "entry_point_selector": hex(get_selector_from_name("get_balance")),
-            "calldata": ["a", "b", "123"],
-            "block_hash": "latest"
+            "request": {
+                "contract_address": pad_zero(contract_address),
+                "entry_point_selector": hex(get_selector_from_name("get_balance")),
+                "calldata": ["a", "b", "123"],
+            },
+            "block_id": "latest"
         }
     )
 
@@ -91,8 +100,7 @@ def test_call_raises_on_invalid_calldata(deploy_info):
     }
 
 
-# This test will fail since we are throwing a custom error block_hash different from `latest`
-@pytest.mark.xfail
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_call_raises_on_incorrect_block_hash(deploy_info):
     """
     Call contract with incorrect block hash
@@ -101,14 +109,16 @@ def test_call_raises_on_incorrect_block_hash(deploy_info):
 
     ex = rpc_call(
         "starknet_call", params={
-            "contract_address": contract_address,
-            "entry_point_selector": hex(get_selector_from_name("get_balance")),
-            "calldata": [],
-            "block_hash": "0x0"
+            "request": {
+                "contract_address": pad_zero(contract_address),
+                "entry_point_selector": hex(get_selector_from_name("get_balance")),
+                "calldata": [],
+            },
+            "block_id": "0x0"
         }
     )
 
     assert ex["error"] == {
-        "code": 24,
-        "message": "Invalid block hash"
+        "code": -1,
+        "message": "Calls with block_id != 'latest' are not supported currently."
     }
diff --git a/test/rpc/test_rpc_class.py b/test/rpc/test_rpc_class.py
index 764ea684f..2d2a33cef 100644
--- a/test/rpc/test_rpc_class.py
+++ b/test/rpc/test_rpc_class.py
@@ -1,10 +1,14 @@
 """
 Tests RPC contract class
 """
+import pytest
+from starkware.starknet.services.api.gateway.transaction_utils import decompress_program
 
-from .rpc_utils import rpc_call
+from starknet_devnet.blueprints.rpc.utils import BlockId
+from .rpc_utils import rpc_call, pad_zero
 
 
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_get_class(class_hash):
     """
     Test get contract class
@@ -18,47 +22,53 @@ def test_get_class(class_hash):
     assert contract_class["entry_points_by_type"] == {
         "CONSTRUCTOR": [],
         "EXTERNAL": [
-            {"offset": "0x03a", "selector": "0x0362398bec32bc0ebb411203221a35a0301193a96f317ebe5e40be9f60d15320"},
-            {"offset": "0x05b", "selector": "0x039e11d48192e4333233c7eb19d10ad67c362bb28580c604d67884c85da39695"}
+            {"offset": "0x3a", "selector": "0x0362398bec32bc0ebb411203221a35a0301193a96f317ebe5e40be9f60d15320"},
+            {"offset": "0x5b", "selector": "0x039e11d48192e4333233c7eb19d10ad67c362bb28580c604d67884c85da39695"}
         ],
         "L1_HANDLER": []
     }
     assert isinstance(contract_class["program"], str)
+    decompress_program({"contract_class": contract_class}, False)
 
 
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_get_class_hash_at(deploy_info, class_hash):
     """
     Test get contract class at given hash
     """
     contract_address: str = deploy_info["address"]
+    block_id: BlockId = "latest"
 
     resp = rpc_call(
         "starknet_getClassHashAt",
-        params={"contract_address": contract_address}
+        params={"contract_address": pad_zero(contract_address), "block_id": block_id}
     )
     rpc_class_hash = resp["result"]
 
     assert rpc_class_hash == class_hash
 
 
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_get_class_at(deploy_info):
     """
     Test get contract class at given contract address
     """
     contract_address: str = deploy_info["address"]
+    block_id: BlockId = "latest"
 
     resp = rpc_call(
         "starknet_getClassAt",
-        params={"contract_address": contract_address}
+        params={"contract_address": pad_zero(contract_address), "block_id": block_id}
     )
     contract_class = resp["result"]
 
     assert contract_class["entry_points_by_type"] == {
         "CONSTRUCTOR": [],
         "EXTERNAL": [
-            {"offset": "0x03a", "selector": "0x0362398bec32bc0ebb411203221a35a0301193a96f317ebe5e40be9f60d15320"},
-            {"offset": "0x05b", "selector": "0x039e11d48192e4333233c7eb19d10ad67c362bb28580c604d67884c85da39695"}
+            {"offset": "0x3a", "selector": "0x0362398bec32bc0ebb411203221a35a0301193a96f317ebe5e40be9f60d15320"},
+            {"offset": "0x5b", "selector": "0x039e11d48192e4333233c7eb19d10ad67c362bb28580c604d67884c85da39695"}
         ],
         "L1_HANDLER": []
     }
     assert isinstance(contract_class["program"], str)
+    decompress_program({"contract_class": contract_class}, False)
diff --git a/test/rpc/test_rpc_estimate_fee.py b/test/rpc/test_rpc_estimate_fee.py
new file mode 100644
index 000000000..a924fb187
--- /dev/null
+++ b/test/rpc/test_rpc_estimate_fee.py
@@ -0,0 +1,150 @@
+"""
+Tests RPC estimate fee
+"""
+
+from __future__ import annotations
+
+from test.rpc.rpc_utils import rpc_call_background_devnet
+from test.shared import CONTRACT_PATH
+from test.util import deploy
+
+import pytest
+
+from starkware.starknet.definitions import constants
+from starkware.starknet.public.abi import get_selector_from_name
+
+from starknet_devnet.blueprints.rpc.structures.payloads import RpcInvokeTransaction
+from starknet_devnet.constants import DEFAULT_GAS_PRICE
+
+
+def common_estimate_response(response):
+    """Expected response from RPC estimate_fee request"""
+    result = response["result"]
+    gas_price: int = int(result["gas_price"], 16)
+    overall_fee: int = int(result["overall_fee"], 16)
+    gas_consumed: int = int(result["gas_consumed"], 16)
+
+    assert gas_price == DEFAULT_GAS_PRICE
+    assert overall_fee == gas_consumed * gas_price
+
+
+@pytest.mark.usefixtures("run_devnet_in_background")
+@pytest.mark.parametrize("run_devnet_in_background", [["--gas-price", str(DEFAULT_GAS_PRICE)]], indirect=True)
+def test_estimate_happy_path():
+    """Happy path estimate_fee call"""
+    deploy_info = deploy(CONTRACT_PATH, ["0"])
+
+    txn: RpcInvokeTransaction = {
+        "contract_address": deploy_info["address"],
+        "entry_point_selector": hex(get_selector_from_name("sum_point_array")),
+        "calldata": ["0x02", "0x01", "0x02", "0x03", "0x04"],
+        # It is not verified and might be removed in next RPC version
+        "transaction_hash": "0x00",
+        "max_fee": "0x00",
+        "version": hex(constants.TRANSACTION_VERSION),
+        "signature": [],
+        "nonce": "0x00",
+        "type": "INVOKE",
+    }
+    response = rpc_call_background_devnet(
+        "starknet_estimateFee", {"request": txn, "block_id": "latest"}
+    )
+
+    common_estimate_response(response)
+
+
+@pytest.mark.usefixtures("run_devnet_in_background")
+def test_estimate_fee_with_genesis_block(rpc_invoke_tx_common):
+    """Call without transaction, expect pass with gas_price zero"""
+    txn: RpcInvokeTransaction = {
+        "contract_address": "0x62230ea046a9a5fbc261ac77d03c8d41e5d442db2284587570ab46455fd2488",
+        "entry_point_selector": "0x2f0b3c5710379609eb5495f1ecd348cb28167711b73609fe565a72734550354",
+        "calldata": ["0x0a", "0x014", "0x00"],
+        **rpc_invoke_tx_common,
+    }
+    response = rpc_call_background_devnet(
+        "starknet_estimateFee", {"request": txn, "block_id": "latest"}
+    )
+
+    common_estimate_response(response)
+
+
+@pytest.mark.usefixtures("run_devnet_in_background")
+def test_estimate_fee_with_invalid_call_data(rpc_invoke_tx_common):
+    """Call estimate fee with invalid data on body"""
+    deploy_info = deploy(CONTRACT_PATH, ["0"])
+
+    txn: RpcInvokeTransaction = {
+        "contract_address": deploy_info["address"],
+        "entry_point_selector": hex(get_selector_from_name("sum_point_array")),
+        "calldata": ["10", "20"],
+        **rpc_invoke_tx_common
+    }
+    ex = rpc_call_background_devnet(
+        "starknet_estimateFee", {"request": txn, "block_id": "latest"}
+    )
+
+    assert ex["error"] == {
+        "code": 22,
+        "message": "Invalid call data"
+    }
+
+
+@pytest.mark.usefixtures("run_devnet_in_background")
+def test_estimate_fee_with_invalid_contract_address(rpc_invoke_tx_common):
+    """Call estimate fee with invalid data on body"""
+    txn: RpcInvokeTransaction = {
+        "contract_address": "0x01",
+        "entry_point_selector": hex(get_selector_from_name("sum_point_array")),
+        "calldata": ["0x02", "0x01", "0x02", "0x03", "0x04"],
+        **rpc_invoke_tx_common,
+    }
+    ex = rpc_call_background_devnet(
+        "starknet_estimateFee", {"request": txn, "block_id": "latest"}
+    )
+
+    assert ex["error"] == {
+        "code": 20,
+        "message": "Contract not found"
+    }
+
+
+@pytest.mark.usefixtures("run_devnet_in_background")
+def test_estimate_fee_with_invalid_message_selector(rpc_invoke_tx_common):
+    """Call estimate fee with invalid data on body"""
+    deploy_info = deploy(CONTRACT_PATH, ["0"])
+
+    txn: RpcInvokeTransaction = {
+        "contract_address": deploy_info["address"],
+        "entry_point_selector": "0x01",
+        "calldata": ["0x02", "0x01", "0x02", "0x03", "0x04"],
+        **rpc_invoke_tx_common,
+    }
+    ex = rpc_call_background_devnet(
+        "starknet_estimateFee", {"request": txn, "block_id": "latest"}
+    )
+
+    assert ex["error"] == {
+        "code": 21,
+        "message": "Invalid message selector"
+    }
+
+
+@pytest.mark.usefixtures("run_devnet_in_background")
+@pytest.mark.parametrize("run_devnet_in_background", [["--gas-price", str(DEFAULT_GAS_PRICE)]], indirect=True)
+def test_estimate_fee_with_complete_request_data(rpc_invoke_tx_common):
+    """Estimate fee with complete request data"""
+
+    deploy_info = deploy(CONTRACT_PATH, ["0"])
+
+    txn: RpcInvokeTransaction = {
+        "contract_address": deploy_info["address"],
+        "entry_point_selector": "0x362398bec32bc0ebb411203221a35a0301193a96f317ebe5e40be9f60d15320",
+        "calldata": ["0x0a", "0x014"],
+        **rpc_invoke_tx_common,
+    }
+    response = rpc_call_background_devnet(
+        "starknet_estimateFee", {"request": txn, "block_id": "latest"}
+    )
+
+    common_estimate_response(response)
diff --git a/test/rpc/test_rpc_misc.py b/test/rpc/test_rpc_misc.py
index 7bcda74d6..15247bea9 100644
--- a/test/rpc/test_rpc_misc.py
+++ b/test/rpc/test_rpc_misc.py
@@ -4,17 +4,19 @@
 
 from __future__ import annotations
 
-import json
-
+import pytest
 from starkware.starknet.public.abi import get_storage_var_address
 from starkware.starknet.core.os.class_hash import compute_class_hash
 
+from starknet_devnet.blueprints.rpc.structures.types import BlockHashDict
 from starknet_devnet.general_config import DEFAULT_GENERAL_CONFIG
 
 from .rpc_utils import rpc_call, gateway_call, get_block_with_transaction, pad_zero
 
 
-def test_get_state_update_by_hash(deploy_info, invoke_info, contract_class):
+# pylint: disable=too-many-locals
+@pytest.mark.usefixtures("run_devnet_in_background")
+def test_get_state_update(deploy_info, invoke_info, contract_class):
     """
     Get state update for the block
     """
@@ -24,15 +26,18 @@ def test_get_state_update_by_hash(deploy_info, invoke_info, contract_class):
     contract_address: str = deploy_info["address"]
     block_with_deploy_hash: str = pad_zero(block_with_deploy["block_hash"])
     block_with_invoke_hash: str = pad_zero(block_with_invoke["block_hash"])
-    block_with_deploy_timestamp: int = block_with_deploy["timestamp"]
-    block_with_invoke_timestamp: int = block_with_invoke["timestamp"]
+    block_id_deploy = BlockHashDict(block_hash=block_with_deploy_hash)
+    block_id_invoke = BlockHashDict(block_hash=block_with_invoke_hash)
+    class_hash = pad_zero(hex(compute_class_hash(contract_class)))
+
+    storage = gateway_call("get_storage_at", contractAddress=contract_address, key=get_storage_var_address("balance"))
 
     new_root_deploy = "0x0" + gateway_call("get_state_update", blockHash=block_with_deploy_hash)["new_root"].lstrip("0")
     new_root_invoke = "0x0" + gateway_call("get_state_update", blockHash=block_with_invoke_hash)["new_root"].lstrip("0")
 
     resp = rpc_call(
-        "starknet_getStateUpdateByHash", params={
-            "block_hash": block_with_deploy_hash
+        "starknet_getStateUpdate", params={
+            "block_id": block_id_deploy
         }
     )
     state_update = resp["result"]
@@ -41,22 +46,25 @@ def test_get_state_update_by_hash(deploy_info, invoke_info, contract_class):
     assert state_update["new_root"] == new_root_deploy
     assert "old_root" in state_update
     assert isinstance(state_update["old_root"], str)
-    assert state_update["accepted_time"] == block_with_deploy_timestamp
     assert state_update["state_diff"] == {
         "storage_diffs": [],
-        "contracts": [
+        "deployed_contracts": [
             {
-                "address": contract_address,
-                "contract_hash": pad_zero(hex(compute_class_hash(contract_class))),
+                "address": pad_zero(contract_address),
+                "class_hash": class_hash,
+            }
+        ],
+        "declared_contracts": [
+            {
+                "class_hash": class_hash,
             }
         ],
         "nonces": [],
     }
 
-    storage = gateway_call("get_storage_at", contractAddress=contract_address, key=get_storage_var_address("balance"))
     resp = rpc_call(
-        "starknet_getStateUpdateByHash", params={
-            "block_hash": block_with_invoke_hash
+        "starknet_getStateUpdate", params={
+            "block_id": block_id_invoke
         }
     )
     state_update = resp["result"]
@@ -65,54 +73,22 @@ def test_get_state_update_by_hash(deploy_info, invoke_info, contract_class):
     assert state_update["new_root"] == new_root_invoke
     assert "old_root" in state_update
     assert isinstance(state_update["old_root"], str)
-    assert state_update["accepted_time"] == block_with_invoke_timestamp
     assert state_update["state_diff"] == {
         "storage_diffs": [
             {
                 "address": contract_address,
                 "key": pad_zero(hex(get_storage_var_address("balance"))),
-                "value": storage,
+                "value": pad_zero(storage),
             }
         ],
-        "contracts": [],
+        "deployed_contracts": [],
+        "declared_contracts": [],
         "nonces": [],
     }
 
 
-def test_get_code(deploy_info):
-    """
-    Get contract code
-    """
-    contract_address: str = deploy_info["address"]
-    contract: dict = gateway_call(
-        "get_code", contractAddress=contract_address
-    )
-
-    resp = rpc_call(
-        "starknet_getCode", params={"contract_address": contract_address}
-    )
-    code = resp["result"]
-
-    assert code["bytecode"] == contract["bytecode"]
-    assert json.loads(code["abi"]) == contract["abi"]
-
-
-# pylint: disable=unused-argument
-def test_get_code_raises_on_incorrect_contract(deploy_info):
-    """
-    Get contract code by incorrect contract address
-    """
-    ex = rpc_call(
-        "starknet_getCode", params={"contract_address": "0x0"}
-    )
-
-    assert ex["error"] == {
-        "code": 20,
-        "message": "Contract not found"
-    }
-
-
-def test_chain_id(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background")
+def test_chain_id():
     """
     Test chain id
     """
@@ -125,15 +101,13 @@ def test_chain_id(deploy_info):
     assert rpc_chain_id == hex(chain_id)
 
 
-def test_protocol_version(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background")
+def test_syncing():
     """
-    Test protocol version
+    Test syncing
     """
-    protocol_version = "0.15.0"
-
-    resp = rpc_call("starknet_protocolVersion", params={})
-    version_hex: str = resp["result"]
-    version_bytes = bytes.fromhex(version_hex.lstrip("0x"))
-    version = version_bytes.decode("utf-8")
+    resp = rpc_call("starknet_syncing", params={})
+    syncing = resp["result"]
 
-    assert version == protocol_version
+    assert isinstance(syncing, bool)
+    assert syncing is False
diff --git a/test/rpc/test_rpc_storage.py b/test/rpc/test_rpc_storage.py
index cd8302553..0595a3aee 100644
--- a/test/rpc/test_rpc_storage.py
+++ b/test/rpc/test_rpc_storage.py
@@ -5,42 +5,43 @@
 import pytest
 from starkware.starknet.public.abi import get_storage_var_address
 
-from .rpc_utils import rpc_call, get_block_with_transaction
+from .rpc_utils import rpc_call, pad_zero
 
 
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_get_storage_at(deploy_info):
     """
     Get storage at address
     """
     contract_address: str = deploy_info["address"]
     key: str = hex(get_storage_var_address("balance"))
-    block_hash: str = "latest"
+    block_id: str = "latest"
 
     resp = rpc_call(
         "starknet_getStorageAt", params={
-            "contract_address": contract_address,
+            "contract_address": pad_zero(contract_address),
             "key": key,
-            "block_hash": block_hash,
+            "block_id": block_id,
         }
     )
     storage = resp["result"]
 
-    assert storage == "0x0"
+    assert storage == "0x00"
 
 
-# pylint: disable=unused-argument
-def test_get_storage_at_raises_on_incorrect_contract(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background", "deploy_info")
+def test_get_storage_at_raises_on_incorrect_contract():
     """
     Get storage at incorrect contract
     """
     key: str = hex(get_storage_var_address("balance"))
-    block_hash: str = "latest"
+    block_id: str = "latest"
 
     ex = rpc_call(
         "starknet_getStorageAt", params={
-            "contract_address": "0x0",
+            "contract_address": "0x00",
             "key": key,
-            "block_hash": block_hash,
+            "block_id": block_id,
         }
     )
 
@@ -50,38 +51,31 @@ def test_get_storage_at_raises_on_incorrect_contract(deploy_info):
     }
 
 
-# internal workings of get_storage_at would have to be changed for this to work
-# since currently it will (correctly) return 0x0 for any incorrect key
-@pytest.mark.xfail
+# internal workings of get_storage_at would have to be changed for this to work properly
+# since currently it will (correctly) return 0x00 for any incorrect key
+# and it should throw exception with code=23 and message="Invalid storage key"
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_get_storage_at_raises_on_incorrect_key(deploy_info):
     """
     Get storage at incorrect key
     """
-    block = get_block_with_transaction(deploy_info["transaction_hash"])
-
     contract_address: str = deploy_info["address"]
-    block_hash: str = block["block_hash"]
 
-    ex = rpc_call(
+    response = rpc_call(
         "starknet_getStorageAt", params={
-            "contract_address": contract_address,
-            "key": "0x0",
-            "block_hash": block_hash,
+            "contract_address": pad_zero(contract_address),
+            "key": "0x00",
+            "block_id": "latest",
         }
     )
 
-    assert ex["error"] == {
-        "code": 23,
-        "message": "Invalid storage key"
-    }
+    assert response["result"] == "0x00"
 
 
-# This will fail as get_storage_at only supports "latest" as block_hash
-# and will fail with custom exception if other is provided
-@pytest.mark.xfail
-def test_get_storage_at_raises_on_incorrect_block_hash(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background")
+def test_get_storage_at_raises_on_incorrect_block_id(deploy_info):
     """
-    Get storage at incorrect block hash
+    Get storage at incorrect block id
     """
 
     contract_address: str = deploy_info["address"]
@@ -89,13 +83,13 @@ def test_get_storage_at_raises_on_incorrect_block_hash(deploy_info):
 
     ex = rpc_call(
         "starknet_getStorageAt", params={
-            "contract_address": contract_address,
+            "contract_address": pad_zero(contract_address),
             "key": key,
-            "block_hash": "0x0",
+            "block_id": "0x0",
         }
     )
 
     assert ex["error"] == {
-        "code": 24,
-        "message": "Invalid block hash"
+        "code": -1,
+        "message": "Calls with block_id != 'latest' are not supported currently."
     }
diff --git a/test/rpc/test_rpc_transactions.py b/test/rpc/test_rpc_transactions.py
index 4faa250f7..2e3d90e5c 100644
--- a/test/rpc/test_rpc_transactions.py
+++ b/test/rpc/test_rpc_transactions.py
@@ -7,39 +7,64 @@
 from typing import List
 
 from test.shared import INCORRECT_GENESIS_BLOCK_HASH
+
+import pytest
+
 from starkware.starknet.definitions import constants
-from starknet_devnet.blueprints.rpc import RpcContractClass
+
+from starknet_devnet.blueprints.rpc.structures.payloads import RpcContractClass
+from starknet_devnet.blueprints.rpc.structures.types import rpc_txn_type
+from starknet_devnet.blueprints.rpc.utils import rpc_felt
 
 from .rpc_utils import rpc_call, get_block_with_transaction, pad_zero
 
 
+def pad_zero_external_entry_points(contract_class: dict) -> dict:
+    """
+    Pad zero every entry point of type EXTERNAL in contract_class
+    """
+    external_entry_points = contract_class["entry_points_by_type"]["EXTERNAL"]
+    for i, _ in enumerate(external_entry_points):
+        external_entry_points[i]["selector"] = pad_zero(external_entry_points[i]["selector"])
+
+    contract_class["entry_points_by_type"]["EXTERNAL"] = external_entry_points
+
+    return contract_class
+
+
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_get_transaction_by_hash_deploy(deploy_info):
     """
     Get transaction by hash
     """
+    block = get_block_with_transaction(deploy_info["transaction_hash"])
+    block_tx = block["transactions"][0]
     transaction_hash: str = deploy_info["transaction_hash"]
     contract_address: str = deploy_info["address"]
 
     resp = rpc_call(
-        "starknet_getTransactionByHash", params={"transaction_hash": transaction_hash}
+        "starknet_getTransactionByHash", params={"transaction_hash": pad_zero(transaction_hash)}
     )
     transaction = resp["result"]
 
     assert transaction == {
-        "txn_hash": pad_zero(transaction_hash),
-        "contract_address": contract_address,
-        "max_fee": "0x0",
-        "calldata": [],
-        "entry_point_selector": None,
-        "signature": [],
-        "version": "0x0"
+        "transaction_hash": pad_zero(transaction_hash),
+        "class_hash": pad_zero(block_tx["class_hash"]),
+        "version": hex(0),
+        "type": rpc_txn_type(block_tx["type"]),
+        "contract_address": pad_zero(contract_address),
+        "contract_address_salt": pad_zero(block_tx["contract_address_salt"]),
+        "constructor_calldata": [],
     }
 
 
+@pytest.mark.usefixtures("run_devnet_in_background", "deploy_info")
 def test_get_transaction_by_hash_invoke(invoke_info):
     """
     Get transaction by hash
     """
+    block = get_block_with_transaction(invoke_info["transaction_hash"])
+    block_tx = block["transactions"][0]
     transaction_hash: str = invoke_info["transaction_hash"]
     contract_address: str = invoke_info["address"]
     entry_point_selector: str = invoke_info["entry_point_selector"]
@@ -49,64 +74,58 @@ def test_get_transaction_by_hash_invoke(invoke_info):
                            for data in invoke_info["calldata"]]
 
     resp = rpc_call(
-        "starknet_getTransactionByHash", params={"transaction_hash": transaction_hash}
+        "starknet_getTransactionByHash", params={"transaction_hash": pad_zero(transaction_hash)}
     )
     transaction = resp["result"]
 
     assert transaction == {
-        "txn_hash": pad_zero(transaction_hash),
+        "transaction_hash": pad_zero(transaction_hash),
+        "max_fee": pad_zero(block_tx["max_fee"]),
+        "version": hex(0),
+        "signature": signature,
+        "nonce": pad_zero(hex(0)),
+        "type": rpc_txn_type(block_tx["type"]),
         "contract_address": contract_address,
-        "max_fee": "0x0",
-        "calldata": calldata,
         "entry_point_selector": pad_zero(entry_point_selector),
-        "signature": signature,
-        "version": "0x0"
+        "calldata": calldata,
     }
 
 
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_get_transaction_by_hash_declare(declare_info):
     """
     Get transaction by hash
     """
+    block = get_block_with_transaction(declare_info["transaction_hash"])
+    block_tx = block["transactions"][0]
     transaction_hash: str = declare_info["transaction_hash"]
     signature: List[str] = [pad_zero(hex(int(sig)))
                             for sig in declare_info["signature"]]
-    sender_address: str = declare_info["sender_address"]
 
     resp = rpc_call(
-        "starknet_getTransactionByHash", params={"transaction_hash": transaction_hash}
+        "starknet_getTransactionByHash", params={"transaction_hash": pad_zero(transaction_hash)}
     )
     transaction = resp["result"]
 
-    assert transaction["txn_hash"] == pad_zero(transaction_hash)
-    assert transaction["max_fee"] == "0x0"
-    assert transaction["signature"] == signature
-    assert transaction["version"] == "0x0"
-    assert transaction["sender_address"] == pad_zero(sender_address)
-    assert transaction["contract_class"]["entry_points_by_type"] == {
-        "CONSTRUCTOR": [],
-        "EXTERNAL": [
-            {
-                "offset": pad_zero("0x3a"),
-                "selector": pad_zero("0x362398bec32bc0ebb411203221a35a0301193a96f317ebe5e40be9f60d15320")
-            },
-            {
-                "offset": pad_zero("0x5b"),
-                "selector": pad_zero("0x39e11d48192e4333233c7eb19d10ad67c362bb28580c604d67884c85da39695")
-            }
-        ],
-        "L1_HANDLER": []
+    assert transaction == {
+        "transaction_hash": pad_zero(transaction_hash),
+        "max_fee": pad_zero(block_tx["max_fee"]),
+        "version": block_tx["version"],
+        "signature": signature,
+        "nonce": pad_zero(block_tx["nonce"]),
+        "type": rpc_txn_type(block_tx["type"]),
+        "class_hash": pad_zero(block_tx["class_hash"]),
+        "sender_address": pad_zero(block_tx["sender_address"]),
     }
-    assert transaction["contract_class"]["program"] != ""
 
 
-# pylint: disable=unused-argument
-def test_get_transaction_by_hash_raises_on_incorrect_hash(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background", "deploy_info")
+def test_get_transaction_by_hash_raises_on_incorrect_hash():
     """
     Get transaction by incorrect hash
     """
     ex = rpc_call(
-        "starknet_getTransactionByHash", params={"transaction_hash": "0x0"}
+        "starknet_getTransactionByHash", params={"transaction_hash": "0x00"}
     )
 
     assert ex["error"] == {
@@ -115,53 +134,61 @@ def test_get_transaction_by_hash_raises_on_incorrect_hash(deploy_info):
     }
 
 
-def test_get_transaction_by_block_hash_and_index(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background")
+def test_get_transaction_by_block_id_and_index(deploy_info):
     """
-    Get transaction by block hash and transaction index
+    Get transaction by block id and transaction index
     """
     block = get_block_with_transaction(deploy_info["transaction_hash"])
+    block_tx = block["transactions"][0]
     transaction_hash: str = deploy_info["transaction_hash"]
     contract_address: str = deploy_info["address"]
-    block_hash: str = block["block_hash"]
+    block_number: str = block["block_number"]
     index: int = 0
 
     resp = rpc_call(
-        "starknet_getTransactionByBlockHashAndIndex", params={
-            "block_hash": block_hash,
+        "starknet_getTransactionByBlockIdAndIndex", params={
+            "block_id": {
+                "block_number": block_number,
+            },
             "index": index
         }
     )
     transaction = resp["result"]
 
     assert transaction == {
-        "txn_hash": pad_zero(transaction_hash),
-        "contract_address": contract_address,
-        "max_fee": "0x0",
-        "calldata": [],
-        "entry_point_selector": None,
-        "signature": [],
-        "version": "0x0"
+        "class_hash": pad_zero(block_tx["class_hash"]),
+        "constructor_calldata": [pad_zero(tx) for tx in block_tx["constructor_calldata"]],
+        "contract_address": pad_zero(contract_address),
+        "contract_address_salt": pad_zero(block_tx["contract_address_salt"]),
+        "transaction_hash": pad_zero(transaction_hash),
+        "type": rpc_txn_type(block_tx["type"]),
+        "version": hex(0),
     }
 
 
-def test_get_transaction_by_block_hash_and_index_raises_on_incorrect_block_hash(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background")
+def test_get_transaction_by_block_id_and_index_raises_on_incorrect_block_hash():
     """
-    Get transaction by incorrect block hash
+    Get transaction by incorrect block id
     """
     ex = rpc_call(
-        "starknet_getTransactionByBlockHashAndIndex", params={
-            "block_hash": INCORRECT_GENESIS_BLOCK_HASH,
+        "starknet_getTransactionByBlockIdAndIndex", params={
+            "block_id": {
+                "block_hash": pad_zero(INCORRECT_GENESIS_BLOCK_HASH)
+            },
             "index": 0
         }
     )
 
     assert ex["error"] == {
         "code": 24,
-        "message": "Invalid block hash"
+        "message": "Invalid block id"
     }
 
 
-def test_get_transaction_by_block_hash_and_index_raises_on_incorrect_index(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background")
+def test_get_transaction_by_block_id_and_index_raises_on_incorrect_index(deploy_info):
     """
     Get transaction by block hash and incorrect transaction index
     """
@@ -169,8 +196,10 @@ def test_get_transaction_by_block_hash_and_index_raises_on_incorrect_index(deplo
     block_hash: str = block["block_hash"]
 
     ex = rpc_call(
-        "starknet_getTransactionByBlockHashAndIndex", params={
-            "block_hash": block_hash,
+        "starknet_getTransactionByBlockIdAndIndex", params={
+            "block_id": {
+                "block_hash": pad_zero(block_hash),
+            },
             "index": 999999
         }
     )
@@ -181,92 +210,32 @@ def test_get_transaction_by_block_hash_and_index_raises_on_incorrect_index(deplo
     }
 
 
-def test_get_transaction_by_block_number_and_index(deploy_info):
-    """
-    Get transaction by block number and transaction index
-    """
-    transaction_hash: str = deploy_info["transaction_hash"]
-    contract_address: str = deploy_info["address"]
-    block = get_block_with_transaction(transaction_hash)
-    block_number: int = block["block_number"]
-    index: int = 0
-
-    resp = rpc_call(
-        "starknet_getTransactionByBlockNumberAndIndex", params={
-            "block_number": block_number,
-            "index": index
-        }
-    )
-    transaction = resp["result"]
-
-    assert transaction == {
-        "txn_hash": pad_zero(transaction_hash),
-        "contract_address": contract_address,
-        "max_fee": "0x0",
-        "calldata": [],
-        "entry_point_selector": None,
-        "signature": [],
-        "version": "0x0"
-    }
-
-
-def test_get_transaction_by_block_number_and_index_raises_on_incorrect_block_number(deploy_info):
-    """
-    Get transaction by incorrect block number
-    """
-    ex = rpc_call(
-        "starknet_getTransactionByBlockNumberAndIndex", params={
-            "block_number": 99999,
-            "index": 0
-        }
-    )
-
-    assert ex["error"] == {
-        "code": 26,
-        "message": "Invalid block number"
-    }
-
-
-def test_get_transaction_by_block_number_and_index_raises_on_incorrect_index(deploy_info):
-    """
-    Get transaction by block hash and incorrect transaction index
-    """
-    block_number: int = 0
-
-    ex = rpc_call(
-        "starknet_getTransactionByBlockNumberAndIndex", params={
-            "block_number": block_number,
-            "index": 99999
-        }
-    )
-
-    assert ex["error"] == {
-        "code": 27,
-        "message": "Invalid transaction index in a block"
-    }
-
-
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_get_declare_transaction_receipt(declare_info):
     """
     Get transaction receipt
     """
     transaction_hash: str = declare_info["transaction_hash"]
+    block = get_block_with_transaction(transaction_hash)
 
     resp = rpc_call(
         "starknet_getTransactionReceipt", params={
-            "transaction_hash": transaction_hash
+            "transaction_hash": pad_zero(transaction_hash)
         }
     )
     receipt = resp["result"]
 
     assert receipt == {
-        "txn_hash": pad_zero(transaction_hash),
+        "transaction_hash": pad_zero(transaction_hash),
         "status": "ACCEPTED_ON_L2",
-        "statusData": None,
-        "actual_fee": "0x0"
+        "status_data": None,
+        "actual_fee": pad_zero(hex(0)),
+        "block_hash": pad_zero(block["block_hash"]),
+        "block_number": block["block_number"],
     }
 
 
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_get_invoke_transaction_receipt(invoke_info):
     """
     Get transaction receipt
@@ -275,27 +244,28 @@ def test_get_invoke_transaction_receipt(invoke_info):
 
     resp = rpc_call(
         "starknet_getTransactionReceipt", params={
-            "transaction_hash": transaction_hash
+            "transaction_hash": pad_zero(transaction_hash)
         }
     )
     receipt = resp["result"]
 
     # Standard == receipt dict test cannot be done here, because invoke transaction fails since no contracts
-    # are actually deployed on devnet, when running test without @devnet_in_background
-    assert receipt["txn_hash"] == pad_zero(transaction_hash)
-    assert receipt["actual_fee"] == "0x0"
+    # are actually deployed on devnet, when running test without run_devnet_in_background fixture
+    assert receipt["transaction_hash"] == pad_zero(transaction_hash)
+    assert receipt["actual_fee"] == pad_zero(hex(0))
     assert receipt["l1_origin_message"] is None
     assert receipt["events"] == []
     assert receipt["messages_sent"] == []
 
 
-def test_get_transaction_receipt_on_incorrect_hash(deploy_info):
+@pytest.mark.usefixtures("run_devnet_in_background", "deploy_info")
+def test_get_transaction_receipt_on_incorrect_hash():
     """
     Get transaction receipt by incorrect hash
     """
     ex = rpc_call(
         "starknet_getTransactionReceipt", params={
-            "transaction_hash": "0x0"
+            "transaction_hash": rpc_felt(0)
         }
     )
 
@@ -305,27 +275,32 @@ def test_get_transaction_receipt_on_incorrect_hash(deploy_info):
     }
 
 
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_get_deploy_transaction_receipt(deploy_info):
     """
     Get transaction receipt
     """
     transaction_hash: str = deploy_info["transaction_hash"]
+    block = get_block_with_transaction(transaction_hash)
 
     resp = rpc_call(
         "starknet_getTransactionReceipt", params={
-            "transaction_hash": transaction_hash
+            "transaction_hash": pad_zero(transaction_hash)
         }
     )
     receipt = resp["result"]
 
     assert receipt == {
-        "txn_hash": pad_zero(transaction_hash),
+        "transaction_hash": pad_zero(transaction_hash),
         "status": "ACCEPTED_ON_L2",
-        "statusData": None,
-        "actual_fee": "0x0"
+        "status_data": None,
+        "actual_fee": pad_zero(hex(0)),
+        "block_hash": pad_zero(block["block_hash"]),
+        "block_number": block["block_number"],
     }
 
 
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_add_invoke_transaction(invoke_content):
     """
     Add invoke transaction
@@ -334,11 +309,11 @@ def test_add_invoke_transaction(invoke_content):
         "starknet_addInvokeTransaction",
         params={
             "function_invocation": {
-                "contract_address": invoke_content["contract_address"],
-                "entry_point_selector": invoke_content["entry_point_selector"],
-                "calldata": invoke_content["calldata"],
+                "contract_address": pad_zero(invoke_content["contract_address"]),
+                "entry_point_selector": pad_zero(invoke_content["entry_point_selector"]),
+                "calldata": [pad_zero(hex(int(data))) for data in invoke_content["calldata"]],
             },
-            "signature": invoke_content["signature"],
+            "signature": [pad_zero(sig) for sig in invoke_content["signature"]],
             "max_fee": hex(0),
             "version": hex(constants.TRANSACTION_VERSION),
         }
@@ -349,11 +324,13 @@ def test_add_invoke_transaction(invoke_content):
     assert receipt["transaction_hash"][:3] == "0x0"
 
 
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_add_declare_transaction_on_incorrect_contract(declare_content):
     """
     Add declare transaction on incorrect class
     """
     contract_class = declare_content["contract_class"]
+    pad_zero_external_entry_points(contract_class=contract_class)
 
     rpc_contract = RpcContractClass(
         program="",
@@ -374,11 +351,13 @@ def test_add_declare_transaction_on_incorrect_contract(declare_content):
     }
 
 
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_add_declare_transaction(declare_content):
     """
     Add declare transaction
     """
     contract_class = declare_content["contract_class"]
+    pad_zero_external_entry_points(contract_class=contract_class)
 
     rpc_contract = RpcContractClass(
         program=contract_class["program"],
@@ -399,13 +378,15 @@ def test_add_declare_transaction(declare_content):
     assert receipt["class_hash"][:3] == "0x0"
 
 
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_add_deploy_transaction_on_incorrect_contract(deploy_content):
     """
     Add deploy transaction on incorrect class
     """
     contract_definition = deploy_content["contract_definition"]
     salt = deploy_content["contract_address_salt"]
-    calldata = [hex(data) for data in deploy_content["constructor_calldata"]]
+    calldata = [rpc_felt(data) for data in deploy_content["constructor_calldata"]]
+    pad_zero_external_entry_points(contract_class=contract_definition)
 
     rpc_contract = RpcContractClass(
         program="",
@@ -415,7 +396,7 @@ def test_add_deploy_transaction_on_incorrect_contract(deploy_content):
     ex = rpc_call(
         "starknet_addDeployTransaction",
         params={
-            "contract_address_salt": salt,
+            "contract_address_salt": pad_zero(salt),
             "constructor_calldata": calldata,
             "contract_definition": rpc_contract,
         }
@@ -427,13 +408,15 @@ def test_add_deploy_transaction_on_incorrect_contract(deploy_content):
     }
 
 
+@pytest.mark.usefixtures("run_devnet_in_background")
 def test_add_deploy_transaction(deploy_content):
     """
     Add deploy transaction
     """
     contract_definition = deploy_content["contract_definition"]
     salt = deploy_content["contract_address_salt"]
-    calldata = [hex(data) for data in deploy_content["constructor_calldata"]]
+    calldata = [rpc_felt(data) for data in deploy_content["constructor_calldata"]]
+    pad_zero_external_entry_points(contract_class=contract_definition)
 
     rpc_contract = RpcContractClass(
         program=contract_definition["program"],
@@ -443,7 +426,7 @@ def test_add_deploy_transaction(deploy_content):
     resp = rpc_call(
         "starknet_addDeployTransaction",
         params={
-            "contract_address_salt": salt,
+            "contract_address_salt": pad_zero(salt),
             "constructor_calldata": calldata,
             "contract_definition": rpc_contract,
         }
diff --git a/test/util.py b/test/util.py
index 8176b78c2..391a0349b 100644
--- a/test/util.py
+++ b/test/util.py
@@ -2,6 +2,7 @@
 File containing functions that wrap Starknet CLI commands.
 """
 
+import functools
 import json
 import os
 import re
@@ -42,6 +43,7 @@ def devnet_in_background(*devnet_args, **devnet_kwargs):
     Prints devnet output in case of AssertionError.
     """
     def wrapper(func):
+        @functools.wraps(func)
         def inner_wrapper(*args, **kwargs):
             proc = run_devnet_in_background(*devnet_args, **devnet_kwargs)
             try: