Skip to content

Evaluate migrating from near-openapi-client to near-openrpc-client #127

@r-near

Description

@r-near

Background

near-api-rs currently depends on two crates from near-openapi-client-rs:

  • near-openapi-client — the HTTP client (progenitor-generated from OpenAPI spec)
  • near-openapi-types — all RPC types

An alternative exists: near-openrpc-client-rs, which is generated from the OpenRPC spec using typify. It has several advantages that would fix current pain points.

Why consider migrating

1. Better type names and enum variants

The openapi-generated types use opaque Variant0/Variant1 names for untagged enum variants, making code hard to read and fragile:

// openapi (current)
RpcTransactionResponse::Variant0 { ... }
RpcTransactionResponse::Variant1 { ... }
JsonRpcResponseForRpcQueryResponseAndRpcQueryError::Variant0 { result, .. }

// openrpc (proposed)
RpcTransactionResponse::FinalExecutionOutcomeWithReceiptView { ... }
RpcTransactionResponse::FinalExecutionOutcomeView { ... }
RpcTransactionResponse::Empty { final_execution_status }

2. Already handles NONE/INCLUDED transaction responses

The openrpc client's RpcTransactionResponse has an Empty variant that correctly handles send_tx with wait_until: None or Included. The openapi client is missing this variant entirely, which causes deserialization failures (see near-openapi-client-rs#40 and #117).

3. Dedicated EXPERIMENTAL_* endpoints

The openrpc client already has dedicated methods (view_account, view_code, view_state, view_access_key, view_access_key_list, call_function) that call the EXPERIMENTAL_* endpoints directly. This aligns with the goal in #122 to migrate away from the multiplexed query endpoint. Currently, near-api-rs has a complex QueryRequest enum in api/src/common/query/query_request.rs that manually constructs all the ByFinality/ByBlockId variants — this would be replaced by the openrpc client's cleaner request types.

4. Simpler error types

The openrpc client uses a straightforward Error enum (Http, Rpc, Json) with a structured RpcError that exposes name, cause, and cause_name(). The openapi client's error chain involves ErrorWrapperForRpc*Error types with InternalError/RequestValidationError/HandlerError variants that need manual From impls for each RPC method.

5. Simpler client architecture

The openrpc client directly constructs JSON-RPC requests and posts them, whereas the openapi client wraps everything through a progenitor-generated HTTP layer that adds indirection (e.g., requiring JsonRpcRequestForQuery wrappers with id, jsonrpc, method fields to be manually constructed by the consumer).

Type mapping

Types re-exported from near_openapi_types in types/src/lib.rs

near_openapi_types near_openrpc_client::types Notes
AccountView RpcViewAccountResponse Same fields, different name
ContractCodeView RpcViewCodeResponse Same fields, different name
FunctionArgs FunctionArgs Same
RpcBlockResponse RpcBlockResponse Same
RpcTransactionResponse RpcTransactionResponse openrpc adds Empty variant
RpcValidatorResponse RpcValidatorResponse Same
StoreKey StoreKey Same
StoreValue StoreValue Same
TxExecutionStatus TxExecutionStatus Same
ViewStateResult RpcViewStateResponse Same fields, different name
CallResult RpcCallFunctionResponse Same fields, different name

Types used for From/TryFrom conversions in types/src/

All of these exist in both crates with matching fields:
DelegateAction, NonDelegateAction, SignedDelegateAction, SignedTransactionView, ExecutionOutcomeWithIdView, ActionView, AccessKeyView, AccessKey, AccessKeyPermission, AccessKeyPermissionView, FunctionCallPermission, PublicKey, CryptoHash, FinalExecutionOutcomeView, FinalExecutionStatus, ExecutionStatusView, TxExecutionError, AccountView (→ RpcViewAccountResponse), and all action types (CreateAccountAction, DeployContractAction, FunctionCallAction, TransferAction, StakeAction, AddKeyAction, DeleteKeyAction, DeleteAccountAction, DelegateAction, DeployGlobalContractAction, UseGlobalContractAction, AddGasKeyAction, DeleteGasKeyAction, TransferToGasKeyAction, DeterministicStateInitAction, GlobalContractDeployMode, GlobalContractIdentifier, GlobalContractIdentifierView).

Client types used in api/src/

near_openapi_client near_openrpc_client Notes
Client (progenitor HTTP client) NearRpcClient Different interface
Client::new_with_client(url, reqwest_client) NearRpcClient::new(url) Gap: no custom reqwest::Client support
client.query(...) client.view_account(...), client.call_function(...), etc. Dedicated methods instead of multiplexed
client.send_tx(...) client.send_tx(...) Same
client.block(...) client.block(...) Same
client.validators(...) client.validators(...) Same
Error::ErrorResponse, Error::InvalidRequest, etc. Error::Http, Error::Rpc, Error::Json Simpler error model

Types that exist in openapi but NOT in openrpc

Type Used in Notes
RpcQueryRequest (multiplexed enum) query_request.rs Not needed — openrpc uses dedicated request types per method
RpcQueryResponse (union type) query_rpc.rs, handlers/ Not needed — openrpc returns typed responses per method
RpcQueryError utils.rs Replaced by generic RpcError with cause_name()
ErrorWrapperForRpc*Error block_rpc.rs, query_rpc.rs, etc. Not needed — simpler error model
JsonRpcRequestFor* / JsonRpcResponseFor* wrappers All RPC call sites Not needed — client handles JSON-RPC framing internally

Gaps to address in near-openrpc-client-rs

  1. Custom reqwest::Client supportnear-api-rs needs this for auth headers (Bearer tokens, API keys) and custom timeouts. NearRpcClient currently only accepts a URL string. A new_with_client(url, reqwest::Client) constructor needs to be added.

  2. Re-exported type aliases — Some types like AccountView, ContractCodeView, CallResult, ViewStateResult have different names in openrpc (RpcViewAccountResponse, RpcViewCodeResponse, RpcCallFunctionResponse, RpcViewStateResponse). Either add type aliases in openrpc or update near-api-rs to use the new names.

Effort estimate

Medium — mostly mechanical renaming, but with some structural changes:

  • types/src/: Update ~30 From/TryFrom impls to reference near_openrpc_client::types:: instead of near_openapi_types::. Most type shapes match, so this is renaming. A few types like AccountViewRpcViewAccountResponse need name changes in the public API.
  • api/src/common/query/: This is the biggest change. The QueryRequest enum and query_rpc.rs can be significantly simplified by using the openrpc client's dedicated endpoints directly (e.g., client.view_account(RpcViewAccountRequest::FinalityAccountId { ... })). The SimpleQueryRpc trait impl and ErrorWrapperForRpc* conversions can be dropped.
  • api/src/common/send.rs: Update send_tx to use the openrpc client directly. The RpcTransactionResponse handling gets cleaner with named variants + Empty.
  • api/src/config.rs: Update RPCEndpoint::client() to construct NearRpcClient instead of near_openapi_client::Client. Requires adding new_with_client to openrpc first.
  • api/src/errors.rs: Simplify error mapping from openrpc's Error enum.
  • api/src/common/utils.rs: Update retry/critical-error logic. The is_critical_* functions need to use RpcError::cause_name() instead of matching on typed error enums.
  • Cargo.toml: Swap near-openapi-client / near-openapi-types deps for near-openrpc-client.

Estimated at ~2-4 days of work for a developer familiar with the codebase, including testing.

Related issues

  • near-openapi-client-rs#40RpcTransactionResponse missing Empty variant (fixed in openrpc)
  • #117 — Error when sending a transaction (caused by the missing variant)
  • #122 — Migrate away from query endpoint to dedicated EXPERIMENTAL_* endpoints (natural fit for openrpc's API)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    NEW❗

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions