Skip to content

eth/chainlist_provider.cpp

Namespaces

Name
eth
eth::rpc

Functions

Name
json::JsonResult< std::vector< RpcEndpointConfig > > load_chainlist_from_json_text(std::string_view json_text)
Parse chainid.network chains.json into normalized RPC endpoint configs.
std::vector< RpcEndpointConfig > filter_to_configured_chains(std::vector< RpcEndpointConfig > endpoints, const std::vector< uint64_t > & configured_chain_ids)
Filter endpoint configs to only those matching configured chain IDs.

Functions Documentation

function load_chainlist_from_json_text

json::JsonResult< std::vector< RpcEndpointConfig > > load_chainlist_from_json_text(
    std::string_view json_text
)

Parse chainid.network chains.json into normalized RPC endpoint configs.

Parameters:

  • json_text Raw chains.json array text.

Return: Vector of RpcEndpointConfig, or JSON parse error.

Filters deprecated chains, wss:// URLs, and API-key placeholder URLs. Deduplicates by chainId + URL.

function filter_to_configured_chains

std::vector< RpcEndpointConfig > filter_to_configured_chains(
    std::vector< RpcEndpointConfig > endpoints,
    const std::vector< uint64_t > & configured_chain_ids
)

Filter endpoint configs to only those matching configured chain IDs.

Parameters:

  • endpoints Endpoints to filter (consumed by move).
  • configured_chain_ids Set of chain IDs that are configured/provisioned.

Return: Filtered endpoints (only configured chains remain).

Source code

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

#include <eth/chainlist_provider.hpp>

#include <base/json_utility.hpp>

#include <boost/json.hpp>
#include <base/rlp-logger.hpp>

#include <algorithm>
#include <string>
#include <unordered_set>
#include <utility>

namespace eth::rpc {

namespace {

namespace json = rlp::base::json;

template <typename T>
[[nodiscard]] json::JsonResult<T> fail_from(const json::JsonError& error)
{
    return json::outcome::failure( error );
}

[[nodiscard]] const json::JsonSchemaObject& chain_entry_schema()
{
    static const json::JsonSchemaArray kRpcArraySchema{
        json::JsonFieldType::kString,
        nullptr,
        nullptr
    };
    static const json::JsonSchemaObject kChainEntrySchema{{
        {"name",     json::JsonFieldType::kString, true,  std::nullopt, nullptr, nullptr},
        {"chainId",  json::JsonFieldType::kU64,    true,  std::nullopt, nullptr, nullptr},
        {"rpc",      json::JsonFieldType::kArray,  true,  std::nullopt, nullptr, &kRpcArraySchema},
        {"status",   json::JsonFieldType::kString, false, boost::json::value( "active" ), nullptr, nullptr},
        {"shortName", json::JsonFieldType::kString, false, boost::json::value( "" ), nullptr, nullptr}
    }};
    return kChainEntrySchema;
}

[[nodiscard]] inline bool is_filtered_url(std::string_view url) noexcept
{
    if ( url.empty() )
    {
        return true;
    }

    constexpr std::string_view kApiKeyPlaceholders[] = {
        "${INFURA_API_KEY}",
        "${ALCHEMY_API_KEY}",
        "${ANKR_API_KEY}",
        "${POKT_API_KEY}",
        "${BLASTAPI_API_KEY}"
    };

    for ( const auto& placeholder : kApiKeyPlaceholders )
    {
        if ( url.find( placeholder ) != std::string_view::npos )
        {
            return true;
        }
    }

    if ( url.find( "wss://" ) == 0 || url.find( "ws://" ) == 0 )
    {
        return true;
    }

    if ( url.find( "https://" ) != 0 && url.find( "http://" ) != 0 )
    {
        return true;
    }

    return false;
}

[[nodiscard]] json::JsonResult<std::vector<RpcEndpointConfig>> parse_chain_entries(
    const boost::json::array& entries)
{
    std::vector<RpcEndpointConfig> result;
    std::unordered_set<std::string> seen;

    for ( const auto& element : entries )
    {
        if ( !element.is_object() )
        {
            continue;
        }

        const auto& obj = element.as_object();
        const auto parsed = json::parse_schema_object( obj, chain_entry_schema() );
        if ( !parsed )
        {
            auto logger = rlp::base::createLogger( "chainlist_provider" );
            logger->warn( "Skipping chain entry: {}", parsed.error().field );
            continue;
        }

        const auto& entry = parsed.value();

        const auto status = json::get_parsed_string( entry, "status" );
        if ( !status )
        {
            continue;
        }
        if ( status.value() == "deprecated" )
        {
            continue;
        }

        const auto name = json::get_parsed_string( entry, "name" );
        if ( !name )
        {
            continue;
        }

        const auto chain_id = json::get_parsed_u64( entry, "chainId" );
        if ( !chain_id )
        {
            continue;
        }

        const auto rpc_urls = json::get_parsed_array( entry, "rpc" );
        if ( !rpc_urls )
        {
            continue;
        }

        for ( const auto& url_value : rpc_urls.value()->values )
        {
            const auto* url_str = std::get_if<std::string>( &url_value.value );
            if ( url_str == nullptr || url_str->empty() )
            {
                continue;
            }

            if ( is_filtered_url( *url_str ) )
            {
                continue;
            }

            const auto dedup_key = std::to_string( chain_id.value() ) + "|" + *url_str;
            if ( seen.find( dedup_key ) != seen.end() )
            {
                continue;
            }
            seen.insert( dedup_key );

            RpcEndpointConfig endpoint;
            endpoint.chain_name = name.value();
            endpoint.chain_id = chain_id.value();
            endpoint.url_template = *url_str;
            endpoint.priority = 0;
            endpoint.weight = 0;
            endpoint.rate_limit_per_second = 0;
            endpoint.is_paid = false;
            endpoint.is_public = true;
            endpoint.verified = false;
            result.push_back( std::move( endpoint ) );
        }
    }

    return result;
}

} // namespace

json::JsonResult<std::vector<RpcEndpointConfig>> load_chainlist_from_json_text(
    std::string_view json_text)
{
    boost::system::error_code ec;
    const auto root = boost::json::parse( std::string( json_text ), ec );
    if ( ec )
    {
        return json::outcome::failure(
            json::JsonError{json::JsonErrorCode::kParseFailed, {}} );
    }

    if ( !root.is_array() )
    {
        return json::outcome::failure(
            json::JsonError{json::JsonErrorCode::kRootNotObject, {}} );
    }

    return parse_chain_entries( root.as_array() );
}

std::vector<RpcEndpointConfig> filter_to_configured_chains(
    std::vector<RpcEndpointConfig>        endpoints,
    const std::vector<uint64_t>&          configured_chain_ids)
{
    std::unordered_set<uint64_t> configured(
        configured_chain_ids.begin(),
        configured_chain_ids.end() );

    endpoints.erase(
        std::remove_if( endpoints.begin(), endpoints.end(),
            [&configured]( const RpcEndpointConfig& ep )
            {
                return configured.find( ep.chain_id ) == configured.end();
            } ),
        endpoints.end() );

    return endpoints;
}

} // namespace eth::rpc

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