Skip to content

eth/json_rpc.cpp

Namespaces

Name
eth
eth::rpc

Functions

Name
std::string_view block_tag_name(RpcBlockTag tag)
boost::json::object make_json_rpc_request(std::string_view method, boost::json::array params, uint64_t id)
boost::json::object make_eth_chain_id_request(uint64_t id)
std::optional< uint64_t > parse_chain_id_response(std::string_view json_text)
boost::json::object make_get_block_by_number_request(RpcBlockTag tag, uint64_t id)
boost::json::object make_get_logs_request(const EventFilter & filter, uint64_t from_block, uint64_t to_block, uint64_t id)
boost::json::object make_get_transaction_receipt_request(const Hash256 & tx_hash, uint64_t id)
std::optional< uint64_t > parse_block_number_response(std::string_view json_text)
std::optional< std::vector< RpcLog > > parse_get_logs_response(std::string_view json_text)
std::optional< ReceiptResult > parse_transaction_receipt_response(std::string_view json_text)

Functions Documentation

function block_tag_name

std::string_view block_tag_name(
    RpcBlockTag tag
)

function make_json_rpc_request

boost::json::object make_json_rpc_request(
    std::string_view method,
    boost::json::array params,
    uint64_t id
)

function make_eth_chain_id_request

boost::json::object make_eth_chain_id_request(
    uint64_t id
)

function parse_chain_id_response

std::optional< uint64_t > parse_chain_id_response(
    std::string_view json_text
)

function make_get_block_by_number_request

boost::json::object make_get_block_by_number_request(
    RpcBlockTag tag,
    uint64_t id
)

function make_get_logs_request

boost::json::object make_get_logs_request(
    const EventFilter & filter,
    uint64_t from_block,
    uint64_t to_block,
    uint64_t id
)

function make_get_transaction_receipt_request

boost::json::object make_get_transaction_receipt_request(
    const Hash256 & tx_hash,
    uint64_t id
)

function parse_block_number_response

std::optional< uint64_t > parse_block_number_response(
    std::string_view json_text
)

function parse_get_logs_response

std::optional< std::vector< RpcLog > > parse_get_logs_response(
    std::string_view json_text
)

function parse_transaction_receipt_response

std::optional< ReceiptResult > parse_transaction_receipt_response(
    std::string_view json_text
)

Source code

// Copyright 2026 Genius Ventures, Inc.
// SPDX-License-Identifier: MIT

#include <eth/json_rpc.hpp>
#include <base/json_utility.hpp>
#include <base/parse_utility.hpp>

namespace eth::rpc {

namespace {

namespace json = rlp::base::json;

[[nodiscard]] std::optional<uint64_t> parse_quantity_value(std::string_view value)
{
    return rlp::base::parse::uint64_hex(value);
}

template <size_t N>
[[nodiscard]] std::optional<std::array<uint8_t, N>> parse_fixed_hex_value(std::string_view value)
{
    std::array<uint8_t, N> out{};
    if (!rlp::base::parse::hex_array(value, out))
    {
        return std::nullopt;
    }
    return out;
}

[[nodiscard]] std::optional<codec::ByteBuffer> parse_bytes_value(std::string_view value)
{
    return rlp::base::parse::hex_bytes(value);
}

[[nodiscard]] const json::JsonSchemaObject& block_result_schema()
{
    static const json::JsonSchemaObject kSchema{{
        {"number", json::JsonFieldType::kString, true, std::nullopt, nullptr, nullptr}
    }};
    return kSchema;
}

[[nodiscard]] const json::JsonSchemaArray& log_topics_schema()
{
    static const json::JsonSchemaArray kSchema{json::JsonFieldType::kString, nullptr, nullptr};
    return kSchema;
}

[[nodiscard]] const json::JsonSchemaObject& rpc_log_schema()
{
    static const json::JsonSchemaObject kSchema{{
        {"address", json::JsonFieldType::kString, true, std::nullopt, nullptr, nullptr},
        {"topics", json::JsonFieldType::kArray, true, std::nullopt, nullptr, &log_topics_schema()},
        {"data", json::JsonFieldType::kString, true, std::nullopt, nullptr, nullptr},
        {"blockNumber", json::JsonFieldType::kString, true, std::nullopt, nullptr, nullptr},
        {"blockHash", json::JsonFieldType::kString, true, std::nullopt, nullptr, nullptr},
        {"transactionHash", json::JsonFieldType::kString, true, std::nullopt, nullptr, nullptr},
        {"logIndex", json::JsonFieldType::kString, true, std::nullopt, nullptr, nullptr}
    }};
    return kSchema;
}

[[nodiscard]] const json::JsonSchemaArray& logs_schema()
{
    static const json::JsonSchemaArray kSchema{json::JsonFieldType::kObject, &rpc_log_schema(), nullptr};
    return kSchema;
}

[[nodiscard]] const json::JsonSchemaObject& logs_response_schema()
{
    static const json::JsonSchemaObject kSchema{{
        {"result", json::JsonFieldType::kArray, true, std::nullopt, nullptr, &logs_schema()}
    }};
    return kSchema;
}

[[nodiscard]] const json::JsonSchemaObject& block_response_schema()
{
    static const json::JsonSchemaObject kSchema{{
        {"result", json::JsonFieldType::kObject, true, std::nullopt, &block_result_schema(), nullptr}
    }};
    return kSchema;
}

[[nodiscard]] const json::JsonSchemaObject& receipt_result_schema()
{
    static const json::JsonSchemaObject kSchema{{
        {"status", json::JsonFieldType::kString, true, std::nullopt, nullptr, nullptr},
        {"blockNumber", json::JsonFieldType::kString, true, std::nullopt, nullptr, nullptr},
        {"blockHash", json::JsonFieldType::kString, true, std::nullopt, nullptr, nullptr},
        {"transactionHash", json::JsonFieldType::kString, true, std::nullopt, nullptr, nullptr},
        {"logs", json::JsonFieldType::kArray, true, std::nullopt, nullptr, &logs_schema()}
    }};
    return kSchema;
}

[[nodiscard]] const json::JsonSchemaObject& receipt_response_schema()
{
    static const json::JsonSchemaObject kSchema{{
        {"result", json::JsonFieldType::kObject, true, std::nullopt, &receipt_result_schema(), nullptr}
    }};
    return kSchema;
}

[[nodiscard]] std::optional<RpcLog> parse_rpc_log(const json::JsonParsedObject& object)
{
    const auto address_value = json::get_parsed_string(object, "address");
    const auto topics_value = json::get_parsed_array(object, "topics");
    const auto data_value = json::get_parsed_string(object, "data");
    const auto block_number_value = json::get_parsed_string(object, "blockNumber");
    const auto block_hash_value = json::get_parsed_string(object, "blockHash");
    const auto tx_hash_value = json::get_parsed_string(object, "transactionHash");
    const auto log_index_value = json::get_parsed_string(object, "logIndex");
    if (!address_value || !topics_value || !data_value || !block_number_value ||
        !block_hash_value || !tx_hash_value || !log_index_value)
    {
        return std::nullopt;
    }

    const auto address = parse_fixed_hex_value<20>(address_value.value());
    const auto data = parse_bytes_value(data_value.value());
    const auto block_number = parse_quantity_value(block_number_value.value());
    const auto block_hash = parse_fixed_hex_value<32>(block_hash_value.value());
    const auto tx_hash = parse_fixed_hex_value<32>(tx_hash_value.value());
    const auto log_index = parse_quantity_value(log_index_value.value());
    if (!address.has_value() || !data.has_value() || !block_number.has_value()
        || !block_hash.has_value() || !tx_hash.has_value() || !log_index.has_value()
        || *log_index > UINT32_MAX)
    {
        return std::nullopt;
    }

    RpcLog parsed;
    parsed.log.address = *address;
    parsed.log.data = *data;
    parsed.block_number = *block_number;
    parsed.block_hash = *block_hash;
    parsed.tx_hash = *tx_hash;
    parsed.log_index = static_cast<uint32_t>(*log_index);

    parsed.log.topics.reserve(topics_value.value()->values.size());
    for (const auto& topic_value : topics_value.value()->values)
    {
        const auto* topic_string = std::get_if<std::string>(&topic_value.value);
        if (topic_string == nullptr)
        {
            return std::nullopt;
        }
        auto topic = parse_fixed_hex_value<32>(*topic_string);
        if (!topic.has_value())
        {
            return std::nullopt;
        }
        parsed.log.topics.push_back(*topic);
    }
    return parsed;
}

} // namespace

std::string_view block_tag_name(RpcBlockTag tag) noexcept
{
    switch (tag)
    {
        case RpcBlockTag::kLatest: return "latest";
        case RpcBlockTag::kSafe: return "safe";
        case RpcBlockTag::kFinalized: return "finalized";
    }
    return "latest";
}

boost::json::object make_json_rpc_request(
    std::string_view   method,
    boost::json::array params,
    uint64_t           id)
{
    boost::json::object request;
    request["jsonrpc"] = "2.0";
    request["method"] = method;
    request["params"] = std::move(params);
    request["id"] = id;
    return request;
}

boost::json::object make_eth_chain_id_request(uint64_t id)
{
    boost::json::array params;
    return make_json_rpc_request("eth_chainId", std::move(params), id);
}

std::optional<uint64_t> parse_chain_id_response(std::string_view json_text)
{
    static const json::JsonSchemaObject kChainIdResponseSchema{{
        {"result", json::JsonFieldType::kString, true, std::nullopt, nullptr, nullptr}
    }};
    const auto parsed = json::parse_schema_object(json_text, kChainIdResponseSchema);
    if (!parsed)
    {
        return std::nullopt;
    }
    const auto chain_id_hex = json::get_parsed_string(parsed.value(), "result");
    if (!chain_id_hex)
    {
        return std::nullopt;
    }
    return parse_quantity_value(chain_id_hex.value());
}

boost::json::object make_get_block_by_number_request(RpcBlockTag tag, uint64_t id)
{
    boost::json::array params;
    params.emplace_back(block_tag_name(tag));
    params.emplace_back(false);
    return make_json_rpc_request("eth_getBlockByNumber", std::move(params), id);
}

boost::json::object make_get_logs_request(
    const EventFilter& filter,
    uint64_t           from_block,
    uint64_t           to_block,
    uint64_t           id)
{
    boost::json::object filter_json;
    filter_json["fromBlock"] = rlp::base::parse::uint64_hex_quantity(from_block);
    filter_json["toBlock"] = rlp::base::parse::uint64_hex_quantity(to_block);

    if (filter.addresses.size() == 1)
    {
        filter_json["address"] = rlp::base::parse::hex_array_string(filter.addresses.front());
    }
    else if (!filter.addresses.empty())
    {
        boost::json::array addresses;
        for (const auto& address : filter.addresses)
        {
            addresses.emplace_back(rlp::base::parse::hex_array_string(address));
        }
        filter_json["address"] = std::move(addresses);
    }

    if (!filter.topics.empty())
    {
        boost::json::array topics;
        for (const auto& topic : filter.topics)
        {
            if (!topic.has_value())
            {
                topics.emplace_back(nullptr);
                continue;
            }
            topics.emplace_back(rlp::base::parse::hex_array_string(*topic));
        }
        filter_json["topics"] = std::move(topics);
    }

    boost::json::array params;
    params.emplace_back(std::move(filter_json));
    return make_json_rpc_request("eth_getLogs", std::move(params), id);
}

boost::json::object make_get_transaction_receipt_request(
    const Hash256& tx_hash,
    uint64_t       id)
{
    boost::json::array params;
    params.emplace_back(rlp::base::parse::hex_array_string(tx_hash));
    return make_json_rpc_request("eth_getTransactionReceipt", std::move(params), id);
}

std::optional<uint64_t> parse_block_number_response(std::string_view json_text)
{
    const auto parsed = json::parse_schema_object(json_text, block_response_schema());
    if (!parsed)
    {
        return std::nullopt;
    }

    const auto result = json::get_parsed_object(parsed.value(), "result");
    if (!result)
    {
        return std::nullopt;
    }
    const auto number = json::get_parsed_string(*result.value(), "number");
    if (!number)
    {
        return std::nullopt;
    }
    return parse_quantity_value(number.value());
}

std::optional<std::vector<RpcLog>> parse_get_logs_response(std::string_view json_text)
{
    const auto parsed = json::parse_schema_object(json_text, logs_response_schema());
    if (!parsed)
    {
        return std::nullopt;
    }

    const auto logs = json::get_parsed_array(parsed.value(), "result");
    if (!logs)
    {
        return std::nullopt;
    }

    std::vector<RpcLog> parsed_logs;
    parsed_logs.reserve(logs.value()->values.size());
    for (const auto& log_value : logs.value()->values)
    {
        const auto* log_object = std::get_if<json::JsonParsedObjectPtr>(&log_value.value);
        if (log_object == nullptr || *log_object == nullptr)
        {
            return std::nullopt;
        }
        auto log = parse_rpc_log(**log_object);
        if (!log.has_value())
        {
            return std::nullopt;
        }
        parsed_logs.push_back(std::move(*log));
    }
    return parsed_logs;
}

std::optional<ReceiptResult> parse_transaction_receipt_response(std::string_view json_text)
{
    const auto parsed = json::parse_schema_object(json_text, receipt_response_schema());
    if (!parsed)
    {
        return std::nullopt;
    }

    const auto receipt_object = json::get_parsed_object(parsed.value(), "result");
    if (!receipt_object)
    {
        return std::nullopt;
    }

    const auto status_value = json::get_parsed_string(*receipt_object.value(), "status");
    const auto block_number_value = json::get_parsed_string(*receipt_object.value(), "blockNumber");
    const auto block_hash_value = json::get_parsed_string(*receipt_object.value(), "blockHash");
    const auto tx_hash_value = json::get_parsed_string(*receipt_object.value(), "transactionHash");
    const auto logs = json::get_parsed_array(*receipt_object.value(), "logs");
    if (!status_value || !block_number_value || !block_hash_value || !tx_hash_value || !logs)
    {
        return std::nullopt;
    }

    const auto status = parse_quantity_value(status_value.value());
    const auto block_number = parse_quantity_value(block_number_value.value());
    const auto block_hash = parse_fixed_hex_value<32>(block_hash_value.value());
    const auto tx_hash = parse_fixed_hex_value<32>(tx_hash_value.value());
    if (!status.has_value() || !block_number.has_value() || !block_hash.has_value()
        || !tx_hash.has_value())
    {
        return std::nullopt;
    }

    ReceiptResult result;
    result.receipt.status = (*status != 0);
    result.block_number = *block_number;
    result.block_hash = *block_hash;
    result.tx_hash = *tx_hash;
    result.receipt.logs.reserve(logs.value()->values.size());
    result.log_indices.reserve(logs.value()->values.size());

    for (const auto& log_value : logs.value()->values)
    {
        const auto* log_object = std::get_if<json::JsonParsedObjectPtr>(&log_value.value);
        if (log_object == nullptr || *log_object == nullptr)
        {
            return std::nullopt;
        }
        auto log = parse_rpc_log(**log_object);
        if (!log.has_value())
        {
            return std::nullopt;
        }
        result.receipt.logs.push_back(std::move(log->log));
        result.log_indices.push_back(log->log_index);
    }
    return result;
}

} // namespace eth::rpc

Updated on 2026-06-05 at 17:22:19 -0700