In-process Kotlin bindings for Autonomi, generated
from the ant-sdk FFI crate
via UniFFI. Ships as a regular
Android Archive (.aar) with native libraries for the three Android ABIs
Google Play requires + ChromeOS. Talks directly to the network — no daemon
process required.
The SDK is published to a Maven repository hosted on GitHub Pages
(ant-maven). Add the repo, then
the dependency.
In settings.gradle.kts:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven("https://withautonomi.github.io/ant-maven/")
}
}In your app module's build.gradle.kts:
dependencies {
implementation("com.autonomi:ant-android:0.1.0")
// JNA (to call into libant_ffi.so) and kotlinx-coroutines-core are
// pulled in transitively from the POM — no need to declare them.
}That's it — the .so native libraries are inside the AAR; nothing to build
locally.
| ABI | Notes |
|---|---|
arm64-v8a |
Modern Android devices — required by Google Play. |
armeabi-v7a |
Older 32-bit ARM devices — required by Google Play. |
x86_64 |
Android emulator on Intel hosts + ChromeOS. |
x86 (32-bit Intel) is deprecated and not shipped.
Minimum compileSdk: 34. Minimum minSdk: 24 (Android 7.0).
Everything starts from a Client. Pick the constructor that matches how you
pay for uploads.
On Android, every call below also needs
dataDir = context.filesDir.absolutePath. The table leaves it out to keep
the signatures short. Without it, each connect fails with HomeDirNotFound
(see Android data directory).
| Your goal | Constructor | You provide |
|---|---|---|
| Production network, read-only | Client.connectDefault() |
dataDir only — uses the bootstrap seeds bundled into the SDK |
| Production network, the app holds the key | Client.connectDefaultWithWallet(privateKey) |
key + dataDir (EVM config preset to arbitrum-one) |
| Production network, the user's wallet signs | Client.connectDefaultForExternalSigner() |
dataDir only — see the external-signer section below |
| Download / read public data only | Client.connect(peers) |
bootstrap peers + dataDir |
| Upload, the app holds the key | Client.connectWithWallet(peers, privateKey, rpcUrl, paymentTokenAddress, paymentVaultAddress) |
key + EVM config + dataDir |
| Upload, the user's wallet signs (WalletConnect) | Client.connectForExternalSigner(peers, rpcUrl, paymentTokenAddress, paymentVaultAddress) |
EVM config (no key) + dataDir — see below |
| Local testing against a devnet | Client.connectLocal() or Client.connectFromDevnetManifest(path) |
a running devnet + dataDir |
Every method is a suspend fun — call from a coroutine. A failure surfaces as
a typed ClientException.
Android app processes have no HOME environment variable, and the SDK's
native core derives its local state paths (bootstrap cache, config) from it.
Without one, every connect* call fails with
ClientException.InitializationFailed (HomeDirNotFound).
Pass the app's files directory as dataDir on every connect* call:
val client = Client.connectDefault(dataDir = context.filesDir.absolutePath)The SDK keeps its local state (bootstrap cache) there and sets HOME for the
native core itself.
Only on v0.0.7 (no dataDir argument) do you need to plant HOME
yourself, once before the first connect call — libc setenv via JNA:
import com.sun.jna.Library
import com.sun.jna.Native
internal interface LibC : Library {
fun setenv(name: String, value: String, overwrite: Int): Int
companion object { val INSTANCE: LibC = Native.load("c", LibC::class.java) }
}
// Once, before the first connect call (e.g. in Application.onCreate):
LibC.INSTANCE.setenv("HOME", context.filesDir.absolutePath, 1)Drop the shim when you upgrade. (Running both during a migration is harmless
— the SDK simply overwrites HOME with the dataDir value.)
import uniffi.ant_ffi.Client
import kotlinx.coroutines.runBlocking
runBlocking {
val client = Client.connectLocal(dataDir = context.filesDir.absolutePath)
val put = client.chunkPut("hello autonomi".toByteArray())
val bytes = client.chunkGet(put.address)
println("stored at ${put.address}")
}val client = Client.connectWithWallet(
peers = listOf(/* network bootstrap multiaddrs */),
privateKey = "0x…",
rpcUrl = "https://…",
paymentTokenAddress = "0x…", // ANT token
paymentVaultAddress = "0x…", // payment vault
dataDir = context.filesDir.absolutePath,
)
// Public: the data map is stored on the network; retrieve by address.
val pub = client.dataPutPublic(payload, PaymentMode.AUTO)
val back = client.dataGetPublic(pub.address)
// Private: you keep the returned hex data map; it's the only way back in.
val priv = client.dataPutPrivate(payload, PaymentMode.AUTO)
val secret = client.dataGetPrivate(priv.dataMap)For anything larger than a small blob, upload from a file path rather than
loading bytes into memory. ant-core streams the file through self-encryption
and spills chunks to disk, so memory stays flat regardless of file size — the
in-memory dataPut* / chunkPut APIs hold the whole payload in RAM.
// Preview the cost before paying (sampled — fast, confidence-aware).
val est = client.estimateFileCost(path, PaymentMode.AUTO)
println("${est.chunkCount} chunks · ~${est.storageCostAtto} atto ANT · ${est.confidence}")
// Public: retrievable by address.
val put = client.fileUploadPublic(path, PaymentMode.AUTO)
// Private: keep the returned hex data map — it's the only way back in.
val priv = client.fileUploadPrivate(path, PaymentMode.AUTO)
// Download straight to disk (streams; ProgressListener is required).
val noop = object : ProgressListener { override fun onProgress(update: ProgressUpdate) {} }
val bytes = client.downloadPublicToFile(put.address, outPath, noop)CostEstimate fields: fileSize, chunkCount, storageCostAtto (storage in
atto-ANT), estimatedGasCostWei, paymentMode, and confidence — a
CostConfidence (PRICED_SAMPLE, VERIFIED_ALL_ALREADY_STORED,
ALL_SAMPLES_ALREADY_STORED_INCOMPLETE) telling you how firm the estimate is,
so you don't render a best-effort 0 as "free".
Memory model. File uploads and
download*ToFilestream (constant memory). ThedataPut*/dataGet*/chunk*APIs load the full payload into aByteArray— fine for small data, avoid for large. Prefer the file-path APIs on mobile.
try {
client.dataGetPublic(addr)
} catch (e: ClientException.NotFound) {
// address isn't on the network
} catch (e: ClientException.NetworkException) {
// transient — safe to retry
} catch (e: ClientException.PaymentException) {
// …
}UniFFI renames the Rust *Error variants to *Exception in Kotlin:
NetworkException, PaymentException, InternalException; NotFound,
PartialUpload, InvalidInput, Timeout, WalletNotConfigured keep their
names. PartialUpload also carries chunksStored / chunksFailed /
totalChunks / storageCostAtto / gasCostWei.
connectForExternalSigner never holds a key. The flow is: prepareFileUpload
→ paymentTransactions(uploadId) (the SDK returns the ready-to-sign approve +
pay transactions — you never build calldata) → sign each with the user's
wallet and waitForReceipt → finalizeUpload (wave) or finalizeUploadMerkle
(merkle). All ABI encoding, receipt polling, and the merkle-winner lookup live
in the SDK. Note waitForReceipt / merkleWinnerPoolHash / networkInfo are
free functions (import uniffi.ant_ffi.*); only paymentTransactions /
prepare* / finalize* are Client methods.
The full step-by-step flow, with Kotlin and Swift worked examples, wave vs
merkle routing, and the paid-but-not-stored retry contract, is documented in
ant-sdk/docs/mobile-external-signer.md.
See ant-mobile-android
for a complete working reference (WalletConnect wiring, both payment paths, live
progress).
The older
external-signer-flow.mddescribes the antd daemon REST flow (HTTP endpoints, hand-rolled ABI) — it does not apply to this mobile SDK. Use the mobile doc above.
Note: the SDK models uniffi
suspend fns with kotlinx-coroutines. ForDispatchers.Mainin a UI app you may also wantimplementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:…"), which is not transitive from the SDK.
paymentMode: PaymentMode(dataPut*/fileUpload*/estimateFileCost):PaymentMode.AUTO(picks the cheapest batching for the size),SINGLE, orMERKLE.visibility: Visibility(prepareDataUpload/prepareFileUpload):Visibility.PUBLIC(retrieve by address) orPRIVATE(retrieve with the hex data map you keep).- Result enums:
PreparedUploadInfo.paymentTypeis aPaymentType(WAVE_BATCH/MERKLE),TxRequest.kindaTxKind(APPROVE/PAY),ProgressUpdate.phaseaProgressPhase(ENCRYPTING,QUOTING,STORING,RESOLVING,DOWNLOADING). - Addresses & data maps are hex strings. Chunk/data payloads cross the FFI
as
ByteArray.
Upgrading from 0.0.7? 0.1.0 replaced the string parameters and fields above with these enum classes and added
dataDirto everyconnect*; the compiler points at every call site. (The Kotlin exception classes were alwaysNetworkException/PaymentException- earlier versions of this README showed the Rust names.)
Versions are built from ant-sdk's
ffi/ source and published to
ant-maven (the Maven repository
the install snippet above uses) via ant-maven's manual publish-android
workflow. This repo hosts the source and generated bindings; GitHub releases
are not cut here — published AARs and available versions live in ant-maven's
maven-metadata.xml.
The iOS SDK (ant-swift) is
released at the same version.
Dual-licensed under either MIT or Apache 2.0, at your option.