ethers-kt

August 19, 2026 Β· View on GitHub

ethers-kt is an async, high-performance Kotlin Multiplatform library for interacting with EVM-based blockchains. It targets JVM, Android, iOS and macOS.

Features:

  • High Performance: Optimized types and code to minimize the number of allocations and copying.

  • Clean Abstractions: Intuitive, extensible, and easy to use.

  • Async: RPC calls are coroutine-based β€” send() suspends rather than blocking. On JVM and Android you also get blocking (sendAwait) and CompletableFuture (sendAsync) variants as inherited members, so Java callers need no wrapping.

  • Safe: RPC calls return an error object in case of failure, instead of throwing an exception.

  • Smart contract bindings:

    • Generate type-safe smart contract bindings from JSON-ABI files or from Foundry projects.
    • Complete support: events (anonymous), custom errors, structs, receive and fallback functions.
    • Custom errors can be resolved from any source (e.g. generated bindings, 4byte directory, etc...).
    • Payable and non-payable functions/constructors.
  • Batch RPC calls:

    • Batch JSON-RPC: RPC calls can be batched together in a single request, reducing the number of round trips to the server.
    • Multicall: Aggregate multiple smart contract calls into a single call via Multicall3 contract.

Supported platforms

TargetNotes
jvmJava 11 bytecode
androidminSdk 24
iosArm64device
iosX64, iosSimulatorArm64simulator
macosArm64

Everything except ethers-abigen and ethers-signers-gcp is available on every target. Those two are JVM-only by nature β€” the first is build-time code generation, the second wraps the Google Cloud KMS client.

πŸš€ Quickstart

All releases are published to Maven Central. Changelog of each release can be found under Releases.

It's recommended to define BOM platform dependency to ensure that ethers-kt artifacts are compatible with each other.

plugins {
    id("io.kriptal.ethers.abigen-plugin") version "2.0.1"
}

// default values
ethersAbigen {
    directorySource("src/main/abi")
    outputDir = "generated/source/ethers/main/kotlin"
}

// Define a maven repository where the library is published
repositories {
    mavenCentral()

    // for snapshot versions, use the following repository
    //maven { url = uri("https://central.sonatype.com/repository/maven-snapshots/") }
}

dependencies {
    // Define a BOM and its version
    implementation(platform("io.kriptal.ethers:ethers-bom:2.0.1"))

    // Define any required artifacts without version
    implementation("io.kriptal.ethers:ethers-abi")
    implementation("io.kriptal.ethers:ethers-core")
    implementation("io.kriptal.ethers:ethers-providers")
    implementation("io.kriptal.ethers:ethers-signers")
}

In a multiplatform project, add the artifacts to commonMain instead:

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation(project.dependencies.platform("io.kriptal.ethers:ethers-bom:2.0.1"))

            implementation("io.kriptal.ethers:ethers-abi")
            implementation("io.kriptal.ethers:ethers-core")
            implementation("io.kriptal.ethers:ethers-providers")
            implementation("io.kriptal.ethers:ethers-signers")
        }
    }
}

To interact with the chain, you need to create a Provider instance, which is the main entry point for all RPC calls.

// create a provider, using a websocket as underlying transport
val provider = Provider.builder("<WS_URL>").buildAwait().unwrap()

// query the latest block number
val startBlockNum = provider.getBlockNumber().sendAwait().unwrap()
println("Starting at block $startBlockNum")

// subscribe to new blocks, blocking the calling thread. Use "forEachAsync" to stream without blocking the caller.
provider.subscribeNewHeads().sendAwait().unwrap().forEach {
    println("New Block: ${it.number}, ${it.number - startBlockNum} blocks since start")
}

buildAwait and sendAwait block the calling thread and exist only on JVM and Android. From common code β€” or any coroutine β€” use the suspending build() and send() instead:

val provider = Provider.builder("<WS_URL>").build().unwrap()

val startBlockNum = provider.getBlockNumber().send().unwrap()
println("Starting at block $startBlockNum")

// "forEach" consumes on the calling thread on every platform. Its non-blocking counterpart, "forEachAsync",
// is JVM/Android-only - from common code, run this on a dispatcher of your choosing.
provider.subscribeNewHeads().send().unwrap().forEach {
    println("New Block: ${it.number}, ${it.number - startBlockNum} blocks since start")
}

πŸ“¦ Structure

Code is structured into multiple modules, each categorized by its purpose. Below is a brief overview of each module. For a more in-depth explanation, please refer to the individual module's README.md.

  • abi: Provides ABI primitives with encoding/decoding logic for all types supported by the EVM.

  • abigen: Code for generating type-safe smart contract bindings from JSON-ABI files.

  • abigen-plugin: Gradle plugin for generating smart contract bindings during the build process.

  • core: Contains optimized base types.

  • crypto: Includes cryptographic utilities for signing and verifying ECDSA signatures on the secp256k1 curve.

  • ens: Full support for ENS names and avatars, with wildcard resolution and offchain resolution via CCIP-Read. EnsResolver resolves explicitly with typed errors, while EnsMiddleware is a Middleware layer that accepts an ENS name anywhere a call request is expected.

  • providers: Logic for interacting with JSON-RPC API using various transports (HTTP, WebSocket).

  • rlp: Handles the encoding and decoding of RLP.

  • signers: Code for transaction/message signing, allowing multiple signing key sources: hardware wallet, mnemonic or raw private key.

  • signers-gcp: Signer backed by Google Cloud KMS. JVM-only.

  • logger: Lightweight logging facade used across the other modules.

πŸ™‹β€β™‚οΈ Contributing

We are happy to have you here! Opportunities to get involved with ethers-kt are open to everyone, no matter your level of expertise. Please check the CONTRIBUTING.md to get started.

Before submitting a PR make sure to format the code and run all checks using the following command:

./gradlew ktlintFormat check

check builds and tests the Apple targets too, and Kotlin/Native cannot cross-compile those, so the command above only completes on a macOS host. On Linux or Windows, run the JVM and Android half and let CI cover the rest:

./gradlew jvmKotest :ethers-abigen-plugin:test

Formatting is source-set scoped there as well β€” see the java-test job in pull-request-checks.yml for the exact task list CI uses on Linux.

Need help❓

First, check if any of the README files under each module answers your question. If the answer is not there please don't open an issue. Instead, you can open a thread under Discussions.

❀️ Acknowledgements

This library has been made possible thanks to the inspiration provided by the following projects:


Proof of Work: 0x6b0f9ff6f53ec22d8d2d92b1beb193cdc523628951b5c81779fabce9f51db351