|
| 1 | +# cktap-android |
| 2 | + |
| 3 | +This project builds an `.aar` package for the Android platform that provides Kotlin language bindings for the [rust-cktap] library. The Kotlin bindings are generated by the [`cktap-ffi`](../cktap-ffi) crate using [uniffi-rs]. |
| 4 | + |
| 5 | +## How it works |
| 6 | + |
| 7 | +`cktap-android` is a thin Android packaging layer on top of the Rust implementation. The stack looks like this: |
| 8 | + |
| 9 | +``` |
| 10 | +Android app (Kotlin) |
| 11 | + └─ cktap-android Kotlin bindings + prebuilt native libs, shipped as an .aar |
| 12 | + └─ cktap-ffi Rust crate that exports the UniFFI scaffolding (cdylib → libcktap_ffi.so) |
| 13 | + └─ rust-cktap Pure-Rust implementation of the Coinkite Tap Protocol (SATSCARD, TAPSIGNER, SATSCHIP) |
| 14 | +``` |
| 15 | + |
| 16 | +What the build produces: |
| 17 | + |
| 18 | +- **`libcktap_ffi.so`** — the Rust library compiled for each supported Android ABI (`arm64-v8a`, `armeabi-v7a`, `x86_64`), placed under `lib/src/main/jniLibs/<abi>/`. |
| 19 | +- **`cktap_ffi.kt`** — the Kotlin API surface auto-generated by UniFFI from the Rust signatures in `cktap-ffi`, placed under `lib/src/main/kotlin/com/coinkite/cktap/`. |
| 20 | +- **`cktap-android-<version>.aar`** — the final artifact that bundles the generated Kotlin sources together with the native libraries. It depends on [JNA](https://github.com/java-native-access/jna) at runtime to dispatch Kotlin calls through JNI into the Rust functions. |
| 21 | + |
| 22 | +Consumers see plain Kotlin types (`suspend` functions, typed exceptions, data classes) and never interact with the JNI bridge directly — UniFFI handles marshalling in both directions. |
| 23 | + |
| 24 | +## How to Use |
| 25 | + |
| 26 | +Once published to Maven Central, add the following to your Android project's `build.gradle.kts`: |
| 27 | + |
| 28 | +```kotlin |
| 29 | +repositories { |
| 30 | + mavenCentral() |
| 31 | +} |
| 32 | + |
| 33 | +dependencies { |
| 34 | + implementation("org.bitcoindevkit:cktap-android:<version>") |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +### Snapshot releases |
| 39 | + |
| 40 | +To use a snapshot release, add the snapshot repository: |
| 41 | + |
| 42 | +```kotlin |
| 43 | +repositories { |
| 44 | + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") |
| 45 | +} |
| 46 | + |
| 47 | +dependencies { |
| 48 | + implementation("org.bitcoindevkit:cktap-android:<version>-SNAPSHOT") |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +### Local Maven (`~/.m2/repository`) |
| 53 | + |
| 54 | +During development — or until an artifact is published to Maven Central — consume the library from your local Maven repository. Declare `mavenLocal()` **before** `mavenCentral()` so Gradle prefers the locally built snapshot and falls back to Maven Central automatically once a release is available: |
| 55 | + |
| 56 | +```kotlin |
| 57 | +repositories { |
| 58 | + mavenLocal() |
| 59 | + mavenCentral() |
| 60 | +} |
| 61 | + |
| 62 | +dependencies { |
| 63 | + implementation("org.bitcoindevkit:cktap-android:0.1.0-SNAPSHOT") |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +See [How to publish to your local Maven repository](#how-to-publish-to-your-local-maven-repository) below for the publish flow. |
| 68 | + |
| 69 | +## How to build locally |
| 70 | + |
| 71 | +_Note: Kotlin `2.3.10`+, JDK 17, Android SDK API 34+, and Android NDK `27.2.12479018`+ are required._ |
| 72 | + |
| 73 | +1. Clone this repository: |
| 74 | + ```shell |
| 75 | + git clone https://github.com/notmandatory/rust-cktap |
| 76 | + ``` |
| 77 | + |
| 78 | +2. Set up environment variables for Android SDK and NDK: |
| 79 | + ```shell |
| 80 | + # macOS |
| 81 | + export ANDROID_SDK_ROOT=~/Library/Android/sdk |
| 82 | + export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/27.2.12479018 |
| 83 | + ``` |
| 84 | + |
| 85 | +3. Build the native libraries and Kotlin bindings: |
| 86 | + ```shell |
| 87 | + cd cktap-android |
| 88 | + just build macos-aarch64 |
| 89 | + ``` |
| 90 | + |
| 91 | +4. (Optional) Run instrumented tests on a connected emulator/device: |
| 92 | + ```shell |
| 93 | + just test |
| 94 | + ``` |
| 95 | + |
| 96 | +## How to publish to your local Maven repository |
| 97 | + |
| 98 | +This flow works **without any signing keys or Sonatype credentials** and is ideal for local development and testing. |
| 99 | + |
| 100 | +### Prerequisites |
| 101 | + |
| 102 | +- [Rust toolchain](https://rustup.rs/) (`cargo`, `rustup`) |
| 103 | +- [`just`](https://github.com/casey/just) (`brew install just` on macOS) |
| 104 | +- JDK 17 |
| 105 | +- Android SDK API 34+ and Android NDK `27.2.12479018`+ (install via Android Studio → SDK Manager → SDK Tools → NDK) |
| 106 | +- `local.properties` inside `cktap-android/` pointing at the SDK — either create the file with `sdk.dir=/path/to/Android/sdk` or export `ANDROID_SDK_ROOT` |
| 107 | + |
| 108 | +### Steps |
| 109 | + |
| 110 | +1. Export the SDK/NDK environment variables (adjust the NDK version to match what is installed): |
| 111 | + |
| 112 | + ```shell |
| 113 | + export ANDROID_SDK_ROOT=~/Library/Android/sdk |
| 114 | + export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/27.2.12479018 |
| 115 | + ``` |
| 116 | + |
| 117 | +2. Build the Rust library and generate the Kotlin bindings for your host ABI: |
| 118 | + |
| 119 | + ```shell |
| 120 | + cd cktap-android |
| 121 | + just build macos-aarch64 |
| 122 | + ``` |
| 123 | + |
| 124 | + This compiles `libcktap_ffi.so` for `arm64-v8a` and regenerates `lib/src/main/kotlin/com/coinkite/cktap/cktap_ffi.kt` via UniFFI. |
| 125 | + |
| 126 | +3. Publish the AAR to your local Maven repository: |
| 127 | + |
| 128 | + ```shell |
| 129 | + just publish-local # alias for: ./gradlew publishToMavenLocal -P localBuild |
| 130 | + ``` |
| 131 | + |
| 132 | + The `-P localBuild` flag disables GPG signing so no keys or Sonatype credentials are required. The AAR, sources JAR, and POM metadata are deposited under `~/.m2/repository/org/bitcoindevkit/cktap-android/0.1.0-SNAPSHOT/`. |
| 133 | + |
| 134 | +Because the version ends in `-SNAPSHOT`, Gradle revalidates the local Maven cache on every build in consumer projects — re-run `just publish-local` after any change and the next consumer build will pick up the fresh copy automatically. |
| 135 | + |
| 136 | +### Consuming the local build |
| 137 | + |
| 138 | +```kotlin |
| 139 | +repositories { |
| 140 | + mavenLocal() |
| 141 | + mavenCentral() |
| 142 | +} |
| 143 | + |
| 144 | +dependencies { |
| 145 | + implementation("org.bitcoindevkit:cktap-android:0.1.0-SNAPSHOT") |
| 146 | +} |
| 147 | +``` |
| 148 | + |
| 149 | +## How to publish to Maven Central (official release) |
| 150 | + |
| 151 | +This flow **requires signing keys and Sonatype credentials**. Configure them in `~/.gradle/gradle.properties` (outside the repo): |
| 152 | + |
| 153 | +```properties |
| 154 | +# Sonatype Central Publisher Portal tokens |
| 155 | +mavenCentralUsername=<TOKEN_USERNAME> |
| 156 | +mavenCentralPassword=<TOKEN_PASSWORD> |
| 157 | + |
| 158 | +# GPG signing |
| 159 | +signing.gnupg.keyName=<GPG_KEY_ID> |
| 160 | +signing.gnupg.passphrase=<GPG_PASSPHRASE> |
| 161 | +``` |
| 162 | + |
| 163 | +Then: |
| 164 | + |
| 165 | +```shell |
| 166 | +# 1. Update version in lib/build.gradle.kts (remove -SNAPSHOT) |
| 167 | +# 2. Build native libraries for all supported ABIs |
| 168 | +just build macos-aarch64 |
| 169 | + |
| 170 | +# 3. Publish |
| 171 | +just publish-central # alias for: ./gradlew publishAndReleaseToMavenCentral |
| 172 | + |
| 173 | +# 4. Tag and push |
| 174 | +git tag v0.1.0 && git push --tags |
| 175 | +``` |
| 176 | + |
| 177 | +## Known issues |
| 178 | + |
| 179 | +### JNA dependency |
| 180 | + |
| 181 | +Depending on the JVM version used by your consumer project, JNA may not be on the classpath. If you see: |
| 182 | + |
| 183 | +``` |
| 184 | +class file for com.sun.jna.Pointer not found |
| 185 | +``` |
| 186 | + |
| 187 | +Add JNA explicitly: |
| 188 | + |
| 189 | +```kotlin |
| 190 | +dependencies { |
| 191 | + implementation("net.java.dev.jna:jna:5.14.0@aar") |
| 192 | +} |
| 193 | +``` |
| 194 | + |
| 195 | +### x86 emulators |
| 196 | + |
| 197 | +The library currently ships native binaries for `arm64-v8a`, `armeabi-v7a`, and `x86_64`. It does **not** ship 32-bit `x86`. Use an `x86_64` emulator when testing on macOS/Linux x86 hosts. |
| 198 | + |
| 199 | +[rust-cktap]: https://github.com/notmandatory/rust-cktap |
| 200 | +[uniffi-rs]: https://github.com/mozilla/uniffi-rs |
0 commit comments