A type-safe, Kotlin client for the NEAR JSON-RPC API.
- Overview
- Features
- Requirements
- Quickstart
- Generator - Reproduce models & client
- Basic Usage
- Error Handling
- Testing
- Contributing
- Deployment Guide
- License
- Contact & References
-
A type-safe, coroutine-powered Kotlin client for interacting with the NEAR Protocol JSON-RPC API.
-
Built using
Kotlin Multiplatformandkotlinx.serialization, with models and API bindings automatically generated from NEARβs official OpenAPI specification.
| Module | Description |
|---|---|
client |
Coroutine-powered JSON-RPC client with full NEAR RPC method wrappers (auto-generated) |
models |
Kotlin @Serializable RPC request and response types (auto-generated) |
generator |
Tooling for generating Kotlin models and client APIs from NEARβs OpenAPI spec |
π― Type-Safe Design: Kotlin data class and sealed class models generated directly from NEARβs OpenAPI specification for compile-time safety
β‘ Coroutine-First API: All network calls are suspend functions, designed for Kotlin Coroutines (structured concurrency)
π Thread-Safe Concurrency: Client internals are safe for concurrent use (coroutine-friendly synchronization; optional actor model for high concurrency)
π‘οΈ Comprehensive Type System: kotlinx.serialization @Serializable models for all RPC requests and responses, including nullable and optional fields
π¦ Minimal Official Dependencies: Small, explicit dependencies on well-known Kotlin libraries (e.g. kotlinx.serialization, ktor); no heavy frameworks required
π§ͺ Extensive Tests: Unit and integration tests (JUnit + MockK or kotlin.test) with mock responses and fixtures generated from OpenAPI examples
π Auto-Generated Core: Models, request builders, and baseline tests are generated from NEARβs OpenAPI spec
π± Kotlin Multiplatform Friendly: Designed so common modules can be compiled for JVM/Android, iOS (via Kotlin/Native), JS and Desktop using Kotlin Multiplatform (KMP)
This library is built on top of Ktor and Kotlinx Serialization.
So you must add the following Ktor dependencies to your project:
implementation("io.ktor:ktor-client-core:<ktor_version>")
implementation("io.ktor:ktor-client-cio:<ktor_version>") // or another engine (OkHttp, Darwin, etc.)
implementation("io.ktor:ktor-client-content-negotiation:<ktor_version>")
implementation("io.ktor:ktor-serialization-kotlinx-json:<ktor_version>")You can easily add NEAR-RPC-Client-Kotlin to your project using JitPack.
In your project-level build.gradle.kts:
repositories {
maven { url = uri("https://jitpack.io") }
}
In your module-level build.gradle.kts:
dependencies {
implementation("com.github.hosseinkarami-dev:NEAR-RPC-Client-Kotlin:<Version>")
}
The generator module is a CLI that parses NEAR's OpenAPI spec and writes generated Kotlin types and the NearClient implementation.
Run the generator against NEAR's OpenAPI:
./gradlew :generator:run --args="--openapi-url https://raw.githubusercontent.com/near/nearcore/master/chain/jsonrpc/openapi/openapi.json"- Use the
--models-outargument to specify a custom output directory for generated model classes (e.g. --models-out build/generated/models) - Use the
--client-outargument to specify a custom output directory for generated client classes (e.g. --client-out build/generated/client)
Use a single HttpClient instance for the app and call NearClient methods from a coroutine scope (e.g., lifecycleScope).
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import kotlinx.serialization.json.Json
import kotlinx.coroutines.launch
import androidx.lifecycle.lifecycleScope
val httpClient = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true })
}
}
val nearClient = NearClient(
httpClient = httpClient,
baseUrl = "https://rpc.mainnet.near.org" // or "https://rpc.testnet.near.org"
)
lifecycleScope.launch {
val response = nearClient.block(
RpcBlockRequest.BlockId(BlockId.BlockHeight(167440515.toULong()))
)
when (response) {
is RpcResponse.Failure -> {
println("Error: ${response.error}")
}
is RpcResponse.Success -> {
val result = response.getResultOrNull<RpcBlockResponse>()
println("Result: $result")
}
}
}Broadcast tx example:
val signedTx = SignedTransaction("BASE64_SIGNED_TX")
val req = RpcSendTransactionRequest(signedTxBase64 = signedTx, waitUntil = null)
when (val r = nearClient.broadcastTxAsync(req)) {
is RpcResponse.Success -> println("tx hash: ${r.result}")
is RpcResponse.Failure -> println("send failed: ${r.error}")
}Notes
- You can use
getResultOrNull<T>()directly onRpcResponse.
If the response isSuccess, it returns the typed model (e.g.,RpcBlockResponse).
If it'sFailure, it safely returnsnull. - Prefer a single application-wide
HttpClient(don't recreate per request). - Close the client (
httpClient.close()) when your app terminates.
This client leverages Kotlin's sealed class pattern to represent both successful and failed RPC responses in a type-safe and expressive way.
Each RPC call returns an instance of RpcResponse<T>:
sealed class RpcResponse<out T> {
data class Success<T>(val result: T): RpcResponse<T>()
data class Failure(val error: ErrorResult): RpcResponse<Nothing>()
}On success, the response is of type RpcResponse.Success and contains the expected result:
val result = (response as RpcResponse.Success).resultOn failure, the response is of type RpcResponse.Failure, which wraps an ErrorResult instance describing the type of error.
The ErrorResult is a sealed class representing different error categories that can occur while performing an RPC call:
sealed class ErrorResult {
data class Rpc(val error: RpcError): ErrorResult() // JSON-RPC level errors
data class RpcRuntime(val error: String): ErrorResult() // RPC runtime-related errors
data class Http(val statusCode: Int, val body: String? = null): ErrorResult() // Non-2xx HTTP responses
data class Timeout(val cause: Throwable? = null): ErrorResult() // Request timed out
data class Network(val cause: Throwable): ErrorResult() // Network issues (e.g., UnknownHostException)
data class Deserialization(val cause: Throwable, val rawBody: String? = null): ErrorResult() // JSON parsing issues
data class Cancellation(val cause: Throwable? = null): ErrorResult() // Request was cancelled
data class Unknown(val message: String? = null, val cause: Throwable? = null): ErrorResult() // Catch-all for unexpected errors
}Hereβs how to handle both success and different types of errors in an RPC response:
val response = rpcClient.callSomeRpcMethod(params)
when (response) {
is RpcResponse.Success -> {
val result = response.result
println("β
RPC call succeeded: $result")
}
is RpcResponse.Failure -> {
when (val error = response.error) {
is ErrorResult.Rpc -> {
when (error.error) {
is RpcError.HandlerError -> {
println("β RPC Handler Error: ${(error.error as RpcError.HandlerError).message}")
}
is RpcError.InternalError -> {
println("β RPC Internal Error: ${(error.error as RpcError.InternalError).message}")
}
is RpcError.RequestValidationError -> {
println("β RPC Internal Error: ${(error.error as RpcError.RequestValidationError).message}")
}
}
}
is ErrorResult.Http -> {
println("β HTTP Error: Status ${error.statusCode}, body: ${error.body}")
}
is ErrorResult.Timeout -> {
println("β³ Timeout Error: ${error.cause?.message}")
}
is ErrorResult.Network -> {
println("π Network Error: ${error.cause.message}")
}
is ErrorResult.Deserialization -> {
println("π Deserialization Error: ${error.cause.message}, raw body: ${error.rawBody}")
}
is ErrorResult.Cancellation -> {
println("π« Request Cancelled: ${error.cause?.message}")
}
is ErrorResult.Unknown -> {
println("β Unknown Error: ${error.message}, cause: ${error.cause?.message}")
}
is ErrorResult.RpcRuntime -> {
println("β οΈ Runtime RPC Error: ${error.error}")
}
}
}
}This structured approach to error handling makes it easy to differentiate between expected JSON-RPC errors, HTTP/network issues, and unexpected failures β providing a clean and maintainable way to handle failures gracefully in your application.
# Run all tests
./gradlew :generator:test
./gradlew :client:test- Unit Tests: Validate the behavior of models and functions in isolation, including helpers, utilities, and serialization logic.
- Integration Tests: Validate end-to-end behavior of the client using a mock RPC engine, simulating actual RPC calls without requiring a live network connection.
- Decoding Tests: Ensure all
@Serializablemodels correctly parse and map data from mock JSON responses.
For detailed instructions on project structure, CI/CD workflow, versioning, and deployment steps, see the DEPLOYMENT.md file.
Contributions welcome!
- Fork and create a branch.
- If you modify generator, include resulting generated files or open a separate PR to regenerate.
- Add tests for new behaviors.
- Run:
./gradlew build
./gradlew :generator:test
./gradlew :client:test- Open a PR with clear description.
- More details: see the Contributing Overview tab in this repository.
This project is licensed under the Apache-2.0 License. See LICENSE for details.
- JSON-RPC interface: https://docs.near.org/api/rpc/introduction
- Other References: