Repository: bitcoindevkit/bdk-ffi (language: python)

Issue:
Make separate JVM .jar and Android .aar packages
Setup build and test structure similar to `bdk-jni`


Discussion / hints:

Fix patch:
diff --git a/bindings/bdk-kotlin/.gitignore b/bindings/bdk-kotlin/.gitignore
index f2efc759..f9467fc7 100644
--- a/bindings/bdk-kotlin/.gitignore
+++ b/bindings/bdk-kotlin/.gitignore
@@ -1,7 +1,7 @@
-/target
 .idea
 .gradle
 local.properties
 build
 *.so
 *.dylib
+bdk.kt
diff --git a/bindings/bdk-kotlin/android/build.gradle b/bindings/bdk-kotlin/android/build.gradle
new file mode 100644
index 00000000..f3bcdc89
--- /dev/null
+++ b/bindings/bdk-kotlin/android/build.gradle
@@ -0,0 +1,71 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'maven-publish'
+
+android {
+    compileSdkVersion 30
+
+    defaultConfig {
+        minSdkVersion 21
+        targetSdkVersion 30
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles 'consumer-rules.pro'
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+afterEvaluate {
+
+    publishing {
+        publications {
+            // Creates a Maven publication called "release".
+            release(MavenPublication) {
+                // Applies the component for the release build variant.
+                from components.release
+
+                // You can then customize attributes of the publication as shown below.
+                groupId = 'org.bitcoindevkit'
+                artifactId = 'bdk'
+                version = '0.0.1-SNAPSHOT'
+            }
+            // Creates a Maven publication called “debug”.
+            debug(MavenPublication) {
+                // Applies the component for the debug build variant.
+                from components.debug
+
+                groupId = 'org.bitcoindevkit'
+                artifactId = 'bdk-debug'
+                version = '0.0.1-SNAPSHOT'
+            }
+        }
+    }
+}
+
+dependencies {
+    implementation(project(':jvm')) {
+        exclude group: 'net.java.dev.jna', module: 'jna'
+    }
+
+    implementation 'net.java.dev.jna:jna:5.8.0@aar'
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+    implementation 'androidx.appcompat:appcompat:1.3.0'
+    implementation 'androidx.core:core-ktx:1.5.0'
+    api "org.slf4j:slf4j-api:1.7.30"
+
+    androidTestImplementation 'com.github.tony19:logback-android:2.0.0'
+    androidTestImplementation(project(':test-fixtures')) {
+        exclude group: 'net.java.dev.jna', module: 'jna'
+    }
+    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+    androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1'
+}
diff --git a/bindings/bdk-kotlin/android/proguard-rules.pro b/bindings/bdk-kotlin/android/proguard-rules.pro
new file mode 100644
index 00000000..172980cc
--- /dev/null
+++ b/bindings/bdk-kotlin/android/proguard-rules.pro
@@ -0,0 +1,26 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+# for JNA
+-dontwarn java.awt.*
+-keep class com.sun.jna.* { *; }
+-keepclassmembers class * extends com.sun.jna.* { public *; }
diff --git a/bindings/bdk-kotlin/android/src/androidTest/assets/logback.xml b/bindings/bdk-kotlin/android/src/androidTest/assets/logback.xml
new file mode 100644
index 00000000..efb1f6b2
--- /dev/null
+++ b/bindings/bdk-kotlin/android/src/androidTest/assets/logback.xml
@@ -0,0 +1,14 @@
+<configuration>
+  <appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender">
+    <tagEncoder>
+      <pattern>%logger{12}</pattern>
+    </tagEncoder>
+    <encoder>
+      <pattern>[%-20thread] %msg</pattern>
+    </encoder>
+  </appender>
+
+  <root level="DEBUG">
+    <appender-ref ref="logcat" />
+  </root>
+</configuration>
diff --git a/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/bdk/AndroidLibTest.kt b/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/bdk/AndroidLibTest.kt
new file mode 100644
index 00000000..9e1eec0b
--- /dev/null
+++ b/bindings/bdk-kotlin/android/src/androidTest/kotlin/org/bitcoindevkit/bdk/AndroidLibTest.kt
@@ -0,0 +1,21 @@
+package org.bitcoindevkit.bdk
+
+import android.app.Application
+import android.content.Context.MODE_PRIVATE
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class AndroidLibTest : LibTest() {
+    override fun getTestDataDir(): String {
+        val context = ApplicationProvider.getApplicationContext<Application>()
+        return context.getDir("bdk-test", MODE_PRIVATE).toString()
+    }
+
+}
diff --git a/bindings/bdk-kotlin/android/src/main/AndroidManifest.xml b/bindings/bdk-kotlin/android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..c8fa4de2
--- /dev/null
+++ b/bindings/bdk-kotlin/android/src/main/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="org.bitcoindevkit.bdk">
+
+  <uses-permission android:name="android.permission.INTERNET" />
+
+</manifest>
diff --git a/bindings/bdk-kotlin/demo/src/main/resources/darwin-x86-64 b/bindings/bdk-kotlin/demo/src/main/resources/darwin-x86-64
deleted file mode 100755
index 05b29c7a..00000000
Binary files a/bindings/bdk-kotlin/demo/src/main/resources/darwin-x86-64 and /dev/null differ
diff --git a/bindings/bdk-kotlin/jvm/build.gradle b/bindings/bdk-kotlin/jvm/build.gradle
index 94f79276..58eefc85 100644
--- a/bindings/bdk-kotlin/jvm/build.gradle
+++ b/bindings/bdk-kotlin/jvm/build.gradle
@@ -4,6 +4,11 @@ plugins {
   id 'maven-publish'
 }
 
+java {
+    sourceCompatibility = JavaVersion.VERSION_1_8
+    targetCompatibility = JavaVersion.VERSION_1_8
+}
+
 test {
   testLogging {
     events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR"
@@ -18,7 +23,7 @@ dependencies {
   api "org.slf4j:slf4j-api:1.7.30"
   testImplementation "ch.qos.logback:logback-classic:1.2.3"
   testImplementation "ch.qos.logback:logback-core:1.2.3"
-  //testImplementation(project(':test-fixtures'))
+  testImplementation(project(':test-fixtures'))
 }
 
 publishing {
@@ -32,8 +37,3 @@ publishing {
     }
   }
 }
-
-java {
-  sourceCompatibility = JavaVersion.VERSION_1_8
-  targetCompatibility = JavaVersion.VERSION_1_8
-}
diff --git a/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt b/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt
deleted file mode 100644
index 2bce10b9..00000000
--- a/bindings/bdk-kotlin/jvm/src/main/kotlin/uniffi/bdk/bdk.kt
+++ /dev/null
@@ -1,1784 +0,0 @@
-// This file was autogenerated by some hot garbage in the `uniffi` crate.
-// Trust me, you don't want to mess with it!
-
-@file:Suppress("NAME_SHADOWING")
-
-package uniffi.bdk;
-
-// Common helper code.
-//
-// Ideally this would live in a separate .kt file where it can be unittested etc
-// in isolation, and perhaps even published as a re-useable package.
-//
-// However, it's important that the detils of how this helper code works (e.g. the
-// way that different builtin types are passed across the FFI) exactly match what's
-// expected by the Rust code on the other side of the interface. In practice right
-// now that means coming from the exact some version of `uniffi` that was used to
-// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin
-// helpers directly inline like we're doing here.
-
-import com.sun.jna.Library
-import com.sun.jna.Native
-import com.sun.jna.Pointer
-import com.sun.jna.Structure
-import java.nio.ByteBuffer
-import java.nio.ByteOrder
-import java.util.concurrent.atomic.AtomicLong
-import java.util.concurrent.atomic.AtomicBoolean
-import java.util.concurrent.atomic.AtomicReference
-import java.util.concurrent.locks.ReentrantLock
-import kotlin.concurrent.withLock
-
-// This is a helper for safely working with byte buffers returned from the Rust code.
-// A rust-owned buffer is represented by its capacity, its current length, and a
-// pointer to the underlying data.
-
-@Structure.FieldOrder("capacity", "len", "data")
-open class RustBuffer : Structure() {
-    @JvmField var capacity: Int = 0
-    @JvmField var len: Int = 0
-    @JvmField var data: Pointer? = null
-
-    class ByValue : RustBuffer(), Structure.ByValue
-    class ByReference : RustBuffer(), Structure.ByReference
-
-    companion object {
-        internal fun alloc(size: Int = 0) = rustCall() { status ->
-            _UniFFILib.INSTANCE.ffi_bdk_1945_rustbuffer_alloc(size, status)
-        }
-
-        internal fun free(buf: RustBuffer.ByValue) = rustCall() { status ->
-            _UniFFILib.INSTANCE.ffi_bdk_1945_rustbuffer_free(buf, status)
-        }
-
-        internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status ->
-            _UniFFILib.INSTANCE.ffi_bdk_1945_rustbuffer_reserve(buf, additional, status)
-        }
-    }
-
-    @Suppress("TooGenericExceptionThrown")
-    fun asByteBuffer() =
-        this.data?.getByteBuffer(0, this.len.toLong())?.also {
-            it.order(ByteOrder.BIG_ENDIAN)
-        }
-}
-
-// This is a helper for safely passing byte references into the rust code.
-// It's not actually used at the moment, because there aren't many things that you
-// can take a direct pointer to in the JVM, and if we're going to copy something
-// then we might as well copy it into a `RustBuffer`. But it's here for API
-// completeness.
-
-@Structure.FieldOrder("len", "data")
-open class ForeignBytes : Structure() {
-    @JvmField var len: Int = 0
-    @JvmField var data: Pointer? = null
-
-    class ByValue : ForeignBytes(), Structure.ByValue
-}
-
-
-// A helper for structured writing of data into a `RustBuffer`.
-// This is very similar to `java.nio.ByteBuffer` but it knows how to grow
-// the underlying `RustBuffer` on demand.
-//
-// TODO: we should benchmark writing things into a `RustBuffer` versus building
-// up a bytearray and then copying it across.
-
-class RustBufferBuilder() {
-    var rbuf = RustBuffer.ByValue()
-    var bbuf: ByteBuffer? = null
-
-    init {
-        val rbuf = RustBuffer.alloc(16) // Totally arbitrary initial size
-        rbuf.writeField("len", 0)
-        this.setRustBuffer(rbuf)
-    }
-
-    internal fun setRustBuffer(rbuf: RustBuffer.ByValue) {
-        this.rbuf = rbuf
-        this.bbuf = this.rbuf.data?.getByteBuffer(0, this.rbuf.capacity.toLong())?.also {
-            it.order(ByteOrder.BIG_ENDIAN)
-            it.position(rbuf.len)
-        }
-    }
-
-    fun finalize() : RustBuffer.ByValue {
-        val rbuf = this.rbuf
-        // Ensure that the JVM-level field is written through to native memory
-        // before turning the buffer, in case its recipient uses it in a context
-        // JNA doesn't apply its automatic synchronization logic.
-        rbuf.writeField("len", this.bbuf!!.position())
-        this.setRustBuffer(RustBuffer.ByValue())
-        return rbuf
-    }
-
-    fun discard() {
-        val rbuf = this.finalize()
-        RustBuffer.free(rbuf)
-    }
-
-    internal fun reserve(size: Int, write: (ByteBuffer) -> Unit) {
-        // TODO: this will perform two checks to ensure we're not overflowing the buffer:
-        // one here where we check if it needs to grow, and another when we call a write
-        // method on the ByteBuffer. It might be cheaper to use exception-driven control-flow
-        // here, trying the write and growing if it throws a `BufferOverflowException`.
-        // Benchmarking needed.
-        if (this.bbuf!!.position() + size > this.rbuf.capacity) {
-            rbuf.writeField("len", this.bbuf!!.position())
-            this.setRustBuffer(RustBuffer.reserve(this.rbuf, size))
-        }
-        write(this.bbuf!!)
-    }
-
-    fun putByte(v: Byte) {
-        this.reserve(1) { bbuf ->
-            bbuf.put(v)
-        }
-    }
-
-    fun putShort(v: Short) {
-        this.reserve(2) { bbuf ->
-            bbuf.putShort(v)
-        }
-    }
-
-    fun putInt(v: Int) {
-        this.reserve(4) { bbuf ->
-            bbuf.putInt(v)
-        }
-    }
-
-    fun putLong(v: Long) {
-        this.reserve(8) { bbuf ->
-            bbuf.putLong(v)
-        }
-    }
-
-    fun putFloat(v: Float) {
-        this.reserve(4) { bbuf ->
-            bbuf.putFloat(v)
-        }
-    }
-
-    fun putDouble(v: Double) {
-        this.reserve(8) { bbuf ->
-            bbuf.putDouble(v)
-        }
-    }
-
-    fun put(v: ByteArray) {
-        this.reserve(v.size) { bbuf ->
-            bbuf.put(v)
-        }
-    }
-}
-
-// Helpers for reading primitive data types from a bytebuffer.
-
-internal fun<T> liftFromRustBuffer(rbuf: RustBuffer.ByValue, readItem: (ByteBuffer) -> T): T {
-    val buf = rbuf.asByteBuffer()!!
-    try {
-       val item = readItem(buf)
-       if (buf.hasRemaining()) {
-           throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!")
-       }
-       return item
-    } finally {
-        RustBuffer.free(rbuf)
-    }
-}
-
-internal fun<T> lowerIntoRustBuffer(v: T, writeItem: (T, RustBufferBuilder) -> Unit): RustBuffer.ByValue {
-    // TODO: maybe we can calculate some sort of initial size hint?
-    val buf = RustBufferBuilder()
-    try {
-        writeItem(v, buf)
-        return buf.finalize()
-    } catch (e: Throwable) {
-        buf.discard()
-        throw e
-    }
-}
-
-// For every type used in the interface, we provide helper methods for conveniently
-// lifting and lowering that type from C-compatible data, and for reading and writing
-// values of that type in a buffer.
-
-
-
-
-@ExperimentalUnsignedTypes
-internal fun UByte.Companion.lift(v: Byte): UByte {
-    return v.toUByte()
-}
-
-@ExperimentalUnsignedTypes
-internal fun UByte.Companion.read(buf: ByteBuffer): UByte {
-    return UByte.lift(buf.get())
-}
-
-@ExperimentalUnsignedTypes
-internal fun UByte.lower(): Byte {
-    return this.toByte()
-}
-
-@ExperimentalUnsignedTypes
-internal fun UByte.write(buf: RustBufferBuilder) {
-    buf.putByte(this.toByte())
-}
-
-
-
-
-
-@ExperimentalUnsignedTypes
-internal fun UInt.Companion.lift(v: Int): UInt {
-    return v.toUInt()
-}
-
-@ExperimentalUnsignedTypes
-internal fun UInt.Companion.read(buf: ByteBuffer): UInt {
-    return UInt.lift(buf.getInt())
-}
-
-@ExperimentalUnsignedTypes
-internal fun UInt.lower(): Int {
-    return this.toInt()
-}
-
-@ExperimentalUnsignedTypes
-internal fun UInt.write(buf: RustBufferBuilder) {
-    buf.putInt(this.toInt())
-}
-
-
-
-
-
-@ExperimentalUnsignedTypes
-internal fun ULong.Companion.lift(v: Long): ULong {
-    return v.toULong()
-}
-
-@ExperimentalUnsignedTypes
-internal fun ULong.Companion.read(buf: ByteBuffer): ULong {
-    return ULong.lift(buf.getLong())
-}
-
-@ExperimentalUnsignedTypes
-internal fun ULong.lower(): Long {
-    return this.toLong()
-}
-
-@ExperimentalUnsignedTypes
-internal fun ULong.write(buf: RustBufferBuilder) {
-    buf.putLong(this.toLong())
-}
-
-
-
-
-
-internal fun Float.Companion.lift(v: Float): Float {
-    return v
-}
-
-internal fun Float.Companion.read(buf: ByteBuffer): Float {
-    return buf.getFloat()
-}
-
-internal fun Float.lower(): Float {
-    return this
-}
-
-internal fun Float.write(buf: RustBufferBuilder) {
-    buf.putFloat(this)
-}
-
-
-
-
-
-internal fun String.Companion.lift(rbuf: RustBuffer.ByValue): String {
-    try {
-        val byteArr = ByteArray(rbuf.len)
-        rbuf.asByteBuffer()!!.get(byteArr)
-        return byteArr.toString(Charsets.UTF_8)
-    } finally {
-        RustBuffer.free(rbuf)
-    }
-}
-
-internal fun String.Companion.read(buf: ByteBuffer): String {
-    val len = buf.getInt()
-    val byteArr = ByteArray(len)
-    buf.get(byteArr)
-    return byteArr.toString(Charsets.UTF_8)
-}
-
-internal fun String.lower(): RustBuffer.ByValue {
-    val byteArr = this.toByteArray(Charsets.UTF_8)
-    // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us
-    // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`.
-    val rbuf = RustBuffer.alloc(byteArr.size)
-    rbuf.asByteBuffer()!!.put(byteArr)
-    return rbuf
-}
-
-internal fun String.write(buf: RustBufferBuilder) {
-    val byteArr = this.toByteArray(Charsets.UTF_8)
-    buf.putInt(byteArr.size)
-    buf.put(byteArr)
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-// Helper functions for pasing values of type UByte?
-@ExperimentalUnsignedTypes
-internal fun liftOptionalu8(rbuf: RustBuffer.ByValue): UByte? {
-    return liftFromRustBuffer(rbuf) { buf ->
-        readOptionalu8(buf)
-    }
-}
-
-@ExperimentalUnsignedTypes
-internal fun readOptionalu8(buf: ByteBuffer): UByte? {
-    if (buf.get().toInt() == 0) {
-        return null
-    }
-    return UByte.read(buf)
-}
-
-@ExperimentalUnsignedTypes
-internal fun lowerOptionalu8(v: UByte?): RustBuffer.ByValue {
-    return lowerIntoRustBuffer(v) { v, buf ->
-        writeOptionalu8(v, buf)
-    }
-}
-
-@ExperimentalUnsignedTypes
-internal fun writeOptionalu8(v: UByte?, buf: RustBufferBuilder) {
-    if (v == null) {
-        buf.putByte(0)
-    } else {
-        buf.putByte(1)
-        v.write(buf)
-    }
-}
-
-
-
-
-
-
-
-// Helper functions for pasing values of type UInt?
-@ExperimentalUnsignedTypes
-internal fun liftOptionalu32(rbuf: RustBuffer.ByValue): UInt? {
-    return liftFromRustBuffer(rbuf) { buf ->
-        readOptionalu32(buf)
-    }
-}
-
-@ExperimentalUnsignedTypes
-internal fun readOptionalu32(buf: ByteBuffer): UInt? {
-    if (buf.get().toInt() == 0) {
-        return null
-    }
-    return UInt.read(buf)
-}
-
-@ExperimentalUnsignedTypes
-internal fun lowerOptionalu32(v: UInt?): RustBuffer.ByValue {
-    return lowerIntoRustBuffer(v) { v, buf ->
-        writeOptionalu32(v, buf)
-    }
-}
-
-@ExperimentalUnsignedTypes
-internal fun writeOptionalu32(v: UInt?, buf: RustBufferBuilder) {
-    if (v == null) {
-        buf.putByte(0)
-    } else {
-        buf.putByte(1)
-        v.write(buf)
-    }
-}
-
-
-
-
-
-
-
-// Helper functions for pasing values of type ULong?
-@ExperimentalUnsignedTypes
-internal fun liftOptionalu64(rbuf: RustBuffer.ByValue): ULong? {
-    return liftFromRustBuffer(rbuf) { buf ->
-        readOptionalu64(buf)
-    }
-}
-
-@ExperimentalUnsignedTypes
-internal fun readOptionalu64(buf: ByteBuffer): ULong? {
-    if (buf.get().toInt() == 0) {
-        return null
-    }
-    return ULong.read(buf)
-}
-
-@ExperimentalUnsignedTypes
-internal fun lowerOptionalu64(v: ULong?): RustBuffer.ByValue {
-    return lowerIntoRustBuffer(v) { v, buf ->
-        writeOptionalu64(v, buf)
-    }
-}
-
-@ExperimentalUnsignedTypes
-internal fun writeOptionalu64(v: ULong?, buf: RustBufferBuilder) {
-    if (v == null) {
-        buf.putByte(0)
-    } else {
-        buf.putByte(1)
-        v.write(buf)
-    }
-}
-
-
-
-
-
-
-
-// Helper functions for pasing values of type String?
-
-internal fun liftOptionalstring(rbuf: RustBuffer.ByValue): String? {
-    return liftFromRustBuffer(rbuf) { buf ->
-        readOptionalstring(buf)
-    }
-}
-
-
-internal fun readOptionalstring(buf: ByteBuffer): String? {
-    if (buf.get().toInt() == 0) {
-        return null
-    }
-    return String.read(buf)
-}
-
-
-internal fun lowerOptionalstring(v: String?): RustBuffer.ByValue {
-    return lowerIntoRustBuffer(v) { v, buf ->
-        writeOptionalstring(v, buf)
-    }
-}
-
-
-internal fun writeOptionalstring(v: String?, buf: RustBufferBuilder) {
-    if (v == null) {
-        buf.putByte(0)
-    } else {
-        buf.putByte(1)
-        v.write(buf)
-    }
-}
-
-
-
-
-
-
-
-// Helper functions for pasing values of type List<ConfirmedTransaction>
-
-@ExperimentalUnsignedTypes
-internal fun liftSequenceTypeConfirmedTransaction(rbuf: RustBuffer.ByValue): List<ConfirmedTransaction> {
-    return liftFromRustBuffer(rbuf) { buf ->
-        readSequenceTypeConfirmedTransaction(buf)
-    }
-}
-
-@ExperimentalUnsignedTypes
-internal fun readSequenceTypeConfirmedTransaction(buf: ByteBuffer): List<ConfirmedTransaction> {
-    val len = buf.getInt()
-    return List<ConfirmedTransaction>(len) {
-        ConfirmedTransaction.read(buf)
-    }
-}
-
-@ExperimentalUnsignedTypes
-internal fun lowerSequenceTypeConfirmedTransaction(v: List<ConfirmedTransaction>): RustBuffer.ByValue {
-    return lowerIntoRustBuffer(v) { v, buf ->
-        writeSequenceTypeConfirmedTransaction(v, buf)
-    }
-}
-
-@ExperimentalUnsignedTypes
-internal fun writeSequenceTypeConfirmedTransaction(v: List<ConfirmedTransaction>, buf: RustBufferBuilder) {
-    buf.putInt(v.size)
-    v.forEach {
-        it.write(buf)
-    }
-}
-
-
-
-
-@Synchronized
-fun findLibraryName(componentName: String): String {
-    val libOverride = System.getProperty("uniffi.component.${componentName}.libraryOverride")
-    if (libOverride != null) {
-        return libOverride
-    }
-    return "uniffi_bdk"
-}
-
-inline fun <reified Lib : Library> loadIndirect(
-    componentName: String
-): Lib {
-    return Native.load<Lib>(findLibraryName(componentName), Lib::class.java)
-}
-
-// A JNA Library to expose the extern-C FFI definitions.
-// This is an implementation detail which will be called internally by the public API.
-
-internal interface _UniFFILib : Library {
-    companion object {
-        internal val INSTANCE: _UniFFILib by lazy { 
-            loadIndirect<_UniFFILib>(componentName = "bdk")
-            .also { lib: _UniFFILib ->
-                CallbackInterfaceBdkProgressInternals.register(lib)
-                }
-            
-        }
-    }
-
-    fun ffi_bdk_1945_OfflineWallet_object_free(ptr: Pointer,
-    uniffi_out_err: RustCallStatus
-    ): Unit
-
-    fun bdk_1945_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,
-    uniffi_out_err: RustCallStatus
-    ): Pointer
-
-    fun bdk_1945_OfflineWallet_get_new_address(ptr: Pointer,
-    uniffi_out_err: RustCallStatus
-    ): RustBuffer.ByValue
-
-    fun bdk_1945_OfflineWallet_get_balance(ptr: Pointer,
-    uniffi_out_err: RustCallStatus
-    ): Long
-
-    fun bdk_1945_OfflineWallet_sign(ptr: Pointer,psbt: Pointer,
-    uniffi_out_err: RustCallStatus
-    ): Unit
-
-    fun bdk_1945_OfflineWallet_get_transactions(ptr: Pointer,
-    uniffi_out_err: RustCallStatus
-    ): RustBuffer.ByValue
-
-    fun ffi_bdk_1945_OnlineWallet_object_free(ptr: Pointer,
-    uniffi_out_err: RustCallStatus
-    ): Unit
-
-    fun bdk_1945_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue,
-    uniffi_out_err: RustCallStatus
-    ): Pointer
-
-    fun bdk_1945_OnlineWallet_get_new_address(ptr: Pointer,
-    uniffi_out_err: RustCallStatus
-    ): RustBuffer.ByValue
-
-    fun bdk_1945_OnlineWallet_get_balance(ptr: Pointer,
-    uniffi_out_err: RustCallStatus
-    ): Long
-
-    fun bdk_1945_OnlineWallet_sign(ptr: Pointer,psbt: Pointer,
-    uniffi_out_err: RustCallStatus
-    ): Unit
-
-    fun bdk_1945_OnlineWallet_get_transactions(ptr: Pointer,
-    uniffi_out_err: RustCallStatus
-    ): RustBuffer.ByValue
-
-    fun bdk_1945_OnlineWallet_get_network(ptr: Pointer,
-    uniffi_out_err: RustCallStatus
-    ): RustBuffer.ByValue
-
-    fun bdk_1945_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue,
-    uniffi_out_err: RustCallStatus
-    ): Unit
-
-    fun bdk_1945_OnlineWallet_broadcast(ptr: Pointer,psbt: Pointer,
-    uniffi_out_err: RustCallStatus
-    ): RustBuffer.ByValue
-
-    fun ffi_bdk_1945_PartiallySignedBitcoinTransaction_object_free(ptr: Pointer,
-    uniffi_out_err: RustCallStatus
-    ): Unit
-
-    fun bdk_1945_PartiallySignedBitcoinTransaction_new(wallet: Pointer,recipient: RustBuffer.ByValue,amount: Long,
-    uniffi_out_err: RustCallStatus
-    ): Pointer
-
-    fun ffi_bdk_1945_BdkProgress_init_callback(callback_stub: ForeignCallback,
-    uniffi_out_err: RustCallStatus
-    ): Unit
-
-    fun ffi_bdk_1945_rustbuffer_alloc(size: Int,
-    uniffi_out_err: RustCallStatus
-    ): RustBuffer.ByValue
-
-    fun ffi_bdk_1945_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue,
-    uniffi_out_err: RustCallStatus
-    ): RustBuffer.ByValue
-
-    fun ffi_bdk_1945_rustbuffer_free(buf: RustBuffer.ByValue,
-    uniffi_out_err: RustCallStatus
-    ): Unit
-
-    fun ffi_bdk_1945_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int,
-    uniffi_out_err: RustCallStatus
-    ): RustBuffer.ByValue
-
-    
-}
-
-// A handful of classes and functions to support the generated data structures.
-// This would be a good candidate for isolating in its own ffi-support lib.
-
-
-
-// Interface implemented by anything that can contain an object reference.
-//
-// Such types expose a `destroy()` method that must be called to cleanly
-// dispose of the contained objects. Failure to call this method may result
-// in memory leaks.
-//
-// The easiest way to ensure this method is called is to use the `.use`
-// helper method to execute a block and destroy the object at the end.
-interface Disposable {
-    fun destroy()
-}
-
-inline fun <T : Disposable?, R> T.use(block: (T) -> R) =
-    try {
-        block(this)
-    } finally {
-        try {
-            // N.B. our implementation is on the nullable type `Disposable?`.
-            this?.destroy()
-        } catch (e: Throwable) {
-            // swallow
-        }
-    }
-
-// The base class for all UniFFI Object types.
-//
-// This class provides core operations for working with the Rust `Arc<T>` pointer to
-// the live Rust struct on the other side of the FFI.
-//
-// There's some subtlety here, because we have to be careful not to operate on a Rust
-// struct after it has been dropped, and because we must expose a public API for freeing
-// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
-//
-//   * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct.
-//     Method calls need to read this pointer from the object's state and pass it in to
-//     the Rust FFI.
-//
-//   * When an `FFIObject` is no longer needed, its pointer should be passed to a
-//     special destructor function provided by the Rust FFI, which will drop the
-//     underlying Rust struct.
-//
-//   * Given an `FFIObject` instance, calling code is expected to call the special
-//     `destroy` method in order to free it after use, either by calling it explicitly
-//     or by using a higher-level helper like the `use` method. Failing to do so will
-//     leak the underlying Rust struct.
-//
-//   * We can't assume that calling code will do the right thing, and must be prepared
-//     to handle Kotlin method calls executing concurrently with or even after a call to
-//     `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
-//
-//   * We must never allow Rust code to operate on the underlying Rust struct after
-//     the destructor has been called, and must never call the destructor more than once.
-//     Doing so may trigger memory unsafety.
-//
-// If we try to implement this with mutual exclusion on access to the pointer, there is the
-// possibility of a race between a method call and a concurrent call to `destroy`:
-//
-//    * Thread A starts a method call, reads the value of the pointer, but is interrupted
-//      before it can pass the pointer over the FFI to Rust.
-//    * Thread B calls `destroy` and frees the underlying Rust struct.
-//    * Thread A resumes, passing the already-read pointer value to Rust and triggering
-//      a use-after-free.
-//
-// One possible solution would be to use a `ReadWriteLock`, with each method call taking
-// a read lock (and thus allowed to run concurrently) and the special `destroy` method
-// taking a write lock (and thus blocking on live method calls). However, we aim not to
-// generate methods with any hidden blocking semantics, and a `destroy` method that might
-// block if called incorrectly seems to meet that bar.
-//
-// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track
-// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
-// has been called. These are updated according to the following rules:
-//
-//    * The initial value of the counter is 1, indicating a live object with no in-flight calls.
-//      The initial value for the flag is false.
-//
-//    * At the start of each method call, we atomically check the counter.
-//      If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
-//      If it is nonzero them we atomically increment it by 1 and proceed with the method call.
-//
-//    * At the end of each method call, we atomically decrement and check the counter.
-//      If it has reached zero then we destroy the underlying Rust struct.
-//
-//    * When `destroy` is called, we atomically flip the flag from false to true.
-//      If the flag was already true we silently fail.
-//      Otherwise we atomically decrement and check the counter.
-//      If it has reached zero then we destroy the underlying Rust struct.
-//
-// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
-// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
-//
-// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
-// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
-// of the underlying Rust code.
-//
-// In the future we may be able to replace some of this with automatic finalization logic, such as using
-// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is
-// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also
-// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1],
-// so there would still be some complexity here).
-//
-// Sigh...all of this for want of a robust finalization mechanism.
-//
-// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
-//
-abstract class FFIObject(
-    protected val pointer: Pointer
-): Disposable, AutoCloseable {
-
-    val wasDestroyed = AtomicBoolean(false)
-    val callCounter = AtomicLong(1)
-
-    open protected fun freeRustArcPtr() {
-        // To be overridden in subclasses.
-    }
-
-    override fun destroy() {
-        // Only allow a single call to this method.
-        // TODO: maybe we should log a warning if called more than once?
-        if (this.wasDestroyed.compareAndSet(false, true)) {
-            // This decrement always matches the initial count of 1 given at creation time.
-            if (this.callCounter.decrementAndGet() == 0L) {
-                this.freeRustArcPtr()
-            }
-        }
-    }
-
-    @Synchronized
-    override fun close() {
-        this.destroy()
-    }
-
-    internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R {
-        // Check and increment the call counter, to keep the object alive.
-        // This needs a compare-and-set retry loop in case of concurrent updates.
-        do {
-            val c = this.callCounter.get()
-            if (c == 0L) {
-                throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
-            }
-            if (c == Long.MAX_VALUE) {
-                throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
-            }
-        } while (! this.callCounter.compareAndSet(c, c + 1L))
-        // Now we can safely do the method call without the pointer being freed concurrently.
-        try {
-            return block(this.pointer)
-        } finally {
-            // This decrement aways matches the increment we performed above.
-            if (this.callCounter.decrementAndGet() == 0L) {
-                this.freeRustArcPtr()
-            }
-        }
-    }
-}
-
-
-
-internal typealias Handle = Long
-internal class ConcurrentHandleMap<T>(
-    private val leftMap: MutableMap<Handle, T> = mutableMapOf(),
-    private val rightMap: MutableMap<T, Handle> = mutableMapOf()
-) {
-    private val lock = java.util.concurrent.locks.ReentrantLock()
-    private val currentHandle = AtomicLong(0L)
-    private val stride = 1L
-
-    fun insert(obj: T): Handle =
-        lock.withLock {
-            rightMap[obj] ?:
-                currentHandle.getAndAdd(stride)
-                    .also { handle ->
-                        leftMap[handle] = obj
-                        rightMap[obj] = handle
-                    }
-            }
-
-    fun <R> callWithResult(handle: Handle, fn: (T) -> R): R =
-        lock.withLock {
-            leftMap[handle] ?: throw RuntimeException("Panic: handle not in handlemap")
-        }.let { obj ->
-            fn.invoke(obj)
-        }
-
-    fun get(handle: Handle) = lock.withLock {
-        leftMap[handle]
-    }
-
-    fun delete(handle: Handle) { 
-        this.remove(handle)
-    }
-
-    fun remove(handle: Handle): T? =
-        lock.withLock {
-            leftMap.remove(handle)?.let { obj ->
-                rightMap.remove(obj)
-                obj
-            }
-        }
-}
-
-interface ForeignCallback : com.sun.jna.Callback {
-    public fun invoke(handle: Long, method: Int, args: RustBuffer.ByValue): RustBuffer.ByValue
-}
-
-// Magic number for the Rust proxy to call using the same mechanism as every other method,
-// to free the callback once it's dropped by Rust.
-internal const val IDX_CALLBACK_FREE = 0
-
-internal abstract class CallbackInternals<CallbackInterface>(
-    val foreignCallback: ForeignCallback
-) {
-    val handleMap = ConcurrentHandleMap<CallbackInterface>()
-
-    // Registers the foreign callback with the Rust side.
-    // This method is generated for each callback interface.
-    abstract fun register(lib: _UniFFILib)
-
-    fun drop(handle: Long): RustBuffer.ByValue {
-        return handleMap.remove(handle).let { RustBuffer.ByValue() }
-    }
-
-    fun lift(n: Long) = handleMap.get(n)
-
-    fun read(buf: ByteBuffer) = lift(buf.getLong())
-
-    fun lower(v: CallbackInterface) =
-        handleMap.insert(v).also {
-            assert(handleMap.get(it) === v) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." }
-        }
-
-    fun write(v: CallbackInterface, buf: RustBufferBuilder) =
-        buf.putLong(lower(v))
-}
-
-
-// Public interface members begin here.
-// Public facing enums
-
-
-
-
-
-enum class Network {
-    BITCOIN,TESTNET,SIGNET,REGTEST;
-
-    companion object {
-        internal fun lift(rbuf: RustBuffer.ByValue): Network {
-            return liftFromRustBuffer(rbuf) { buf -> Network.read(buf) }
-        }
-
-        internal fun read(buf: ByteBuffer) =
-            try { values()[buf.getInt() - 1] }
-            catch (e: IndexOutOfBoundsException) {
-                throw RuntimeException("invalid enum value, something is very wrong!!", e)
-            }
-    }
-
-    internal fun lower(): RustBuffer.ByValue {
-        return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
-    }
-
-    internal fun write(buf: RustBufferBuilder) {
-        buf.putInt(this.ordinal + 1)
-    }
-}
-
-
-
-
-
-
-
-
-
-sealed class DatabaseConfig  {
-    
-    data class Memory(
-        val junk: String 
-        ) : DatabaseConfig()
-    
-    data class Sled(
-        val config: SledDbConfiguration 
-        ) : DatabaseConfig()
-    
-
-    companion object {
-        internal fun lift(rbuf: RustBuffer.ByValue): DatabaseConfig {
-            return liftFromRustBuffer(rbuf) { buf -> DatabaseConfig.read(buf) }
-        }
-
-        internal fun read(buf: ByteBuffer): DatabaseConfig {
-            return when(buf.getInt()) {
-                1 -> DatabaseConfig.Memory(
-                    String.read(buf)
-                    )
-                2 -> DatabaseConfig.Sled(
-                    SledDbConfiguration.read(buf)
-                    )
-                else -> throw RuntimeException("invalid enum value, something is very wrong!!")
-            }
-        }
-    }
-
-    internal fun lower(): RustBuffer.ByValue {
-        return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
-    }
-
-    internal fun write(buf: RustBufferBuilder) {
-        when(this) {
-            is DatabaseConfig.Memory -> {
-                buf.putInt(1)
-                this.junk.write(buf)
-                
-            }
-            is DatabaseConfig.Sled -> {
-                buf.putInt(2)
-                this.config.write(buf)
-                
-            }
-        }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
-    }
-
-    
-    
-}
-
-
-
-
-
-
-
-
-@ExperimentalUnsignedTypes
-sealed class BlockchainConfig  {
-    
-    data class Electrum(
-        val config: ElectrumConfig 
-        ) : BlockchainConfig()
-    
-    data class Esplora(
-        val config: EsploraConfig 
-        ) : BlockchainConfig()
-    
-
-    companion object {
-        internal fun lift(rbuf: RustBuffer.ByValue): BlockchainConfig {
-            return liftFromRustBuffer(rbuf) { buf -> BlockchainConfig.read(buf) }
-        }
-
-        internal fun read(buf: ByteBuffer): BlockchainConfig {
-            return when(buf.getInt()) {
-                1 -> BlockchainConfig.Electrum(
-                    ElectrumConfig.read(buf)
-                    )
-                2 -> BlockchainConfig.Esplora(
-                    EsploraConfig.read(buf)
-                    )
-                else -> throw RuntimeException("invalid enum value, something is very wrong!!")
-            }
-        }
-    }
-
-    internal fun lower(): RustBuffer.ByValue {
-        return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
-    }
-
-    internal fun write(buf: RustBufferBuilder) {
-        when(this) {
-            is BlockchainConfig.Electrum -> {
-                buf.putInt(1)
-                this.config.write(buf)
-                
-            }
-            is BlockchainConfig.Esplora -> {
-                buf.putInt(2)
-                this.config.write(buf)
-                
-            }
-        }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
-    }
-
-    
-    
-}
-
-// Error definitions
-@Structure.FieldOrder("code", "error_buf")
-internal open class RustCallStatus : Structure() {
-    @JvmField var code: Int = 0
-    @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue()
-
-    fun isSuccess(): Boolean {
-        return code == 0
-    }
-
-    fun isError(): Boolean {
-        return code == 1
-    }
-
-    fun isPanic(): Boolean {
-        return code == 2
-    }
-}
-
-class InternalException(message: String) : Exception(message)
-
-// Each top-level error class has a companion object that can lift the error from the call status's rust buffer
-interface CallStatusErrorHandler<E> {
-    fun lift(error_buf: RustBuffer.ByValue): E;
-}
-
-// Error BdkError
-
-sealed class BdkException(message: String): Exception(message)  {
-        // Each variant is a nested class
-        // Flat enums carries a string error message, so no special implementation is necessary.
-        class InvalidU32Bytes(message: String) : BdkException(message)
-        class Generic(message: String) : BdkException(message)
-        class ScriptDoesntHaveAddressForm(message: String) : BdkException(message)
-        class NoRecipients(message: String) : BdkException(message)
-        class NoUtxosSelected(message: String) : BdkException(message)
-        class OutputBelowDustLimit(message: String) : BdkException(message)
-        class InsufficientFunds(message: String) : BdkException(message)
-        class BnBTotalTriesExceeded(message: String) : BdkException(message)
-        class BnBNoExactMatch(message: String) : BdkException(message)
-        class UnknownUtxo(message: String) : BdkException(message)
-        class TransactionNotFound(message: String) : BdkException(message)
-        class TransactionConfirmed(message: String) : BdkException(message)
-        class IrreplaceableTransaction(message: String) : BdkException(message)
-        class FeeRateTooLow(message: String) : BdkException(message)
-        class FeeTooLow(message: String) : BdkException(message)
-        class FeeRateUnavailable(message: String) : BdkException(message)
-        class MissingKeyOrigin(message: String) : BdkException(message)
-        class Key(message: String) : BdkException(message)
-        class ChecksumMismatch(message: String) : BdkException(message)
-        class SpendingPolicyRequired(message: String) : BdkException(message)
-        class InvalidPolicyPathException(message: String) : BdkException(message)
-        class Signer(message: String) : BdkException(message)
-        class InvalidNetwork(message: String) : BdkException(message)
-        class InvalidProgressValue(message: String) : BdkException(message)
-        class ProgressUpdateException(message: String) : BdkException(message)
-        class InvalidOutpoint(message: String) : BdkException(message)
-        class Descriptor(message: String) : BdkException(message)
-        class AddressValidator(message: String) : BdkException(message)
-        class Encode(message: String) : BdkException(message)
-        class Miniscript(message: String) : BdkException(message)
-        class Bip32(message: String) : BdkException(message)
-        class Secp256k1(message: String) : BdkException(message)
-        class Json(message: String) : BdkException(message)
-        class Hex(message: String) : BdkException(message)
-        class Psbt(message: String) : BdkException(message)
-        class PsbtParse(message: String) : BdkException(message)
-        class Electrum(message: String) : BdkException(message)
-        class Esplora(message: String) : BdkException(message)
-        class Sled(message: String) : BdkException(message)
-        
-
-    companion object ErrorHandler : CallStatusErrorHandler<BdkException> {
-        override fun lift(error_buf: RustBuffer.ByValue): BdkException {
-            return liftFromRustBuffer(error_buf) { error_buf -> read(error_buf) }
-        }
-
-        fun read(error_buf: ByteBuffer): BdkException {
-            
-                return when(error_buf.getInt()) {
-                1 -> BdkException.InvalidU32Bytes(String.read(error_buf))
-                2 -> BdkException.Generic(String.read(error_buf))
-                3 -> BdkException.ScriptDoesntHaveAddressForm(String.read(error_buf))
-                4 -> BdkException.NoRecipients(String.read(error_buf))
-                5 -> BdkException.NoUtxosSelected(String.read(error_buf))
-                6 -> BdkException.OutputBelowDustLimit(String.read(error_buf))
-                7 -> BdkException.InsufficientFunds(String.read(error_buf))
-                8 -> BdkException.BnBTotalTriesExceeded(String.read(error_buf))
-                9 -> BdkException.BnBNoExactMatch(String.read(error_buf))
-                10 -> BdkException.UnknownUtxo(String.read(error_buf))
-                11 -> BdkException.TransactionNotFound(String.read(error_buf))
-                12 -> BdkException.TransactionConfirmed(String.read(error_buf))
-                13 -> BdkException.IrreplaceableTransaction(String.read(error_buf))
-                14 -> BdkException.FeeRateTooLow(String.read(error_buf))
-                15 -> BdkException.FeeTooLow(String.read(error_buf))
-                16 -> BdkException.FeeRateUnavailable(String.read(error_buf))
-                17 -> BdkException.MissingKeyOrigin(String.read(error_buf))
-                18 -> BdkException.Key(String.read(error_buf))
-                19 -> BdkException.ChecksumMismatch(String.read(error_buf))
-                20 -> BdkException.SpendingPolicyRequired(String.read(error_buf))
-                21 -> BdkException.InvalidPolicyPathException(String.read(error_buf))
-                22 -> BdkException.Signer(String.read(error_buf))
-                23 -> BdkException.InvalidNetwork(String.read(error_buf))
-                24 -> BdkException.InvalidProgressValue(String.read(error_buf))
-                25 -> BdkException.ProgressUpdateException(String.read(error_buf))
-                26 -> BdkException.InvalidOutpoint(String.read(error_buf))
-                27 -> BdkException.Descriptor(String.read(error_buf))
-                28 -> BdkException.AddressValidator(String.read(error_buf))
-                29 -> BdkException.Encode(String.read(error_buf))
-                30 -> BdkException.Miniscript(String.read(error_buf))
-                31 -> BdkException.Bip32(String.read(error_buf))
-                32 -> BdkException.Secp256k1(String.read(error_buf))
-                33 -> BdkException.Json(String.read(error_buf))
-                34 -> BdkException.Hex(String.read(error_buf))
-                35 -> BdkException.Psbt(String.read(error_buf))
-                36 -> BdkException.PsbtParse(String.read(error_buf))
-                37 -> BdkException.Electrum(String.read(error_buf))
-                38 -> BdkException.Esplora(String.read(error_buf))
-                39 -> BdkException.Sled(String.read(error_buf))
-                else -> throw RuntimeException("invalid error enum value, something is very wrong!!")
-            }
-        }
-    }
-
-    
-    
-}
-
-
-// Helpers for calling Rust
-// In practice we usually need to be synchronized to call this safely, so it doesn't
-// synchronize itself
-
-// Call a rust function that returns a Result<>.  Pass in the Error class companion that corresponds to the Err
-private inline fun <U, E: Exception> rustCallWithError(errorHandler: CallStatusErrorHandler<E>, callback: (RustCallStatus) -> U): U {
-    var status = RustCallStatus();
-    val return_value = callback(status)
-    if (status.isSuccess()) {
-        return return_value
-    } else if (status.isError()) {
-        throw errorHandler.lift(status.error_buf)
-    } else if (status.isPanic()) {
-        // when the rust code sees a panic, it tries to construct a rustbuffer
-        // with the message.  but if that code panics, then it just sends back
-        // an empty buffer.
-        if (status.error_buf.len > 0) {
-            throw InternalException(String.lift(status.error_buf))
-        } else {
-            throw InternalException("Rust panic")
-        }
-    } else {
-        throw InternalException("Unknown rust call status: $status.code")
-    }
-}
-
-// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR
-object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> {
-    override fun lift(error_buf: RustBuffer.ByValue): InternalException {
-        RustBuffer.free(error_buf)
-        return InternalException("Unexpected CALL_ERROR")
-    }
-}
-
-// Call a rust function that returns a plain value
-private inline fun <U> rustCall(callback: (RustCallStatus) -> U): U {
-    return rustCallWithError(NullCallStatusErrorHandler, callback);
-}
-
-// Public facing records
-
-data class SledDbConfiguration (
-    var path: String, 
-    var treeName: String 
-)  {
-    companion object {
-        internal fun lift(rbuf: RustBuffer.ByValue): SledDbConfiguration {
-            return liftFromRustBuffer(rbuf) { buf -> SledDbConfiguration.read(buf) }
-        }
-
-        internal fun read(buf: ByteBuffer): SledDbConfiguration {
-            return SledDbConfiguration(
-            String.read(buf),
-            String.read(buf)
-            )
-        }
-    }
-
-    internal fun lower(): RustBuffer.ByValue {
-        return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
-    }
-
-    internal fun write(buf: RustBufferBuilder) {
-            this.path.write(buf)
-        
-            this.treeName.write(buf)
-        
-    }
-
-    
-    
-}
-
-@ExperimentalUnsignedTypes
-data class ConfirmedTransaction (
-    var fees: ULong?, 
-    var height: UInt, 
-    var timestamp: ULong, 
-    var received: ULong, 
-    var sent: ULong, 
-    var id: String 
-)  {
-    companion object {
-        internal fun lift(rbuf: RustBuffer.ByValue): ConfirmedTransaction {
-            return liftFromRustBuffer(rbuf) { buf -> ConfirmedTransaction.read(buf) }
-        }
-
-        internal fun read(buf: ByteBuffer): ConfirmedTransaction {
-            return ConfirmedTransaction(
-            readOptionalu64(buf),
-            UInt.read(buf),
-            ULong.read(buf),
-            ULong.read(buf),
-            ULong.read(buf),
-            String.read(buf)
-            )
-        }
-    }
-
-    internal fun lower(): RustBuffer.ByValue {
-        return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
-    }
-
-    internal fun write(buf: RustBufferBuilder) {
-            writeOptionalu64(this.fees, buf)
-        
-            this.height.write(buf)
-        
-            this.timestamp.write(buf)
-        
-            this.received.write(buf)
-        
-            this.sent.write(buf)
-        
-            this.id.write(buf)
-        
-    }
-
-    
-    
-}
-
-@ExperimentalUnsignedTypes
-data class ElectrumConfig (
-    var url: String, 
-    var socks5: String?, 
-    var retry: UByte, 
-    var timeout: UByte?, 
-    var stopGap: ULong 
-)  {
-    companion object {
-        internal fun lift(rbuf: RustBuffer.ByValue): ElectrumConfig {
-            return liftFromRustBuffer(rbuf) { buf -> ElectrumConfig.read(buf) }
-        }
-
-        internal fun read(buf: ByteBuffer): ElectrumConfig {
-            return ElectrumConfig(
-            String.read(buf),
-            readOptionalstring(buf),
-            UByte.read(buf),
-            readOptionalu8(buf),
-            ULong.read(buf)
-            )
-        }
-    }
-
-    internal fun lower(): RustBuffer.ByValue {
-        return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
-    }
-
-    internal fun write(buf: RustBufferBuilder) {
-            this.url.write(buf)
-        
-            writeOptionalstring(this.socks5, buf)
-        
-            this.retry.write(buf)
-        
-            writeOptionalu8(this.timeout, buf)
-        
-            this.stopGap.write(buf)
-        
-    }
-
-    
-    
-}
-
-@ExperimentalUnsignedTypes
-data class EsploraConfig (
-    var baseUrl: String, 
-    var proxy: String?, 
-    var timeoutRead: ULong, 
-    var timeoutWrite: ULong, 
-    var stopGap: ULong 
-)  {
-    companion object {
-        internal fun lift(rbuf: RustBuffer.ByValue): EsploraConfig {
-            return liftFromRustBuffer(rbuf) { buf -> EsploraConfig.read(buf) }
-        }
-
-        internal fun read(buf: ByteBuffer): EsploraConfig {
-            return EsploraConfig(
-            String.read(buf),
-            readOptionalstring(buf),
-            ULong.read(buf),
-            ULong.read(buf),
-            ULong.read(buf)
-            )
-        }
-    }
-
-    internal fun lower(): RustBuffer.ByValue {
-        return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
-    }
-
-    internal fun write(buf: RustBufferBuilder) {
-            this.baseUrl.write(buf)
-        
-            writeOptionalstring(this.proxy, buf)
-        
-            this.timeoutRead.write(buf)
-        
-            this.timeoutWrite.write(buf)
-        
-            this.stopGap.write(buf)
-        
-    }
-
-    
-    
-}
-
-
-// Namespace functions
-
-
-// Objects
-
-@ExperimentalUnsignedTypes
-public interface OfflineWalletInterface {
-    fun getNewAddress(): String
-    fun getBalance(): ULong
-    fun sign(psbt: PartiallySignedBitcoinTransaction )
-    fun getTransactions(): List<ConfirmedTransaction>
-    
-}
-
-@ExperimentalUnsignedTypes
-class OfflineWallet(
-    pointer: Pointer
-) : FFIObject(pointer), OfflineWalletInterface {
-    constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) :
-        this(
-    rustCallWithError(BdkException) { status ->
-    _UniFFILib.INSTANCE.bdk_1945_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status)
-})
-
-    /**
-     * Disconnect the object from the underlying Rust object.
-     * 
-     * It can be called more than once, but once called, interacting with the object
-     * causes an `IllegalStateException`.
-     * 
-     * Clients **must** call this method once done with the object, or cause a memory leak.
-     */
-    override protected fun freeRustArcPtr() {
-        rustCall() { status ->
-            _UniFFILib.INSTANCE.ffi_bdk_1945_OfflineWallet_object_free(this.pointer, status)
-        }
-    }
-
-    internal fun lower(): Pointer = callWithPointer { it }
-
-    internal fun write(buf: RustBufferBuilder) {
-        // The Rust code always expects pointers written as 8 bytes,
-        // and will fail to compile if they don't fit.
-        buf.putLong(Pointer.nativeValue(this.lower()))
-    }
-
-    override fun getNewAddress(): String =
-        callWithPointer {
-    rustCall() { status ->
-    _UniFFILib.INSTANCE.bdk_1945_OfflineWallet_get_new_address(it,  status)
-}
-        }.let {
-            String.lift(it)
-        }
-    
-    override fun getBalance(): ULong =
-        callWithPointer {
-    rustCallWithError(BdkException) { status ->
-    _UniFFILib.INSTANCE.bdk_1945_OfflineWallet_get_balance(it,  status)
-}
-        }.let {
-            ULong.lift(it)
-        }
-    
-    override fun sign(psbt: PartiallySignedBitcoinTransaction ) =
-        callWithPointer {
-    rustCallWithError(BdkException) { status ->
-    _UniFFILib.INSTANCE.bdk_1945_OfflineWallet_sign(it, psbt.lower() , status)
-}
-        }
-    
-    override fun getTransactions(): List<ConfirmedTransaction> =
-        callWithPointer {
-    rustCallWithError(BdkException) { status ->
-    _UniFFILib.INSTANCE.bdk_1945_OfflineWallet_get_transactions(it,  status)
-}
-        }.let {
-            liftSequenceTypeConfirmedTransaction(it)
-        }
-    
-    
-
-    companion object {
-        internal fun lift(ptr: Pointer): OfflineWallet {
-            return OfflineWallet(ptr)
-        }
-
-        internal fun read(buf: ByteBuffer): OfflineWallet {
-            // The Rust code always writes pointers as 8 bytes, and will
-            // fail to compile if they don't fit.
-            return OfflineWallet.lift(Pointer(buf.getLong()))
-        }
-
-        
-    }
-}
-
-@ExperimentalUnsignedTypes
-public interface OnlineWalletInterface {
-    fun getNewAddress(): String
-    fun getBalance(): ULong
-    fun sign(psbt: PartiallySignedBitcoinTransaction )
-    fun getTransactions(): List<ConfirmedTransaction>
-    fun getNetwork(): Network
-    fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? )
-    fun broadcast(psbt: PartiallySignedBitcoinTransaction ): String
-    
-}
-
-@ExperimentalUnsignedTypes
-class OnlineWallet(
-    pointer: Pointer
-) : FFIObject(pointer), OnlineWalletInterface {
-    constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) :
-        this(
-    rustCallWithError(BdkException) { status ->
-    _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status)
-})
-
-    /**
-     * Disconnect the object from the underlying Rust object.
-     * 
-     * It can be called more than once, but once called, interacting with the object
-     * causes an `IllegalStateException`.
-     * 
-     * Clients **must** call this method once done with the object, or cause a memory leak.
-     */
-    override protected fun freeRustArcPtr() {
-        rustCall() { status ->
-            _UniFFILib.INSTANCE.ffi_bdk_1945_OnlineWallet_object_free(this.pointer, status)
-        }
-    }
-
-    internal fun lower(): Pointer = callWithPointer { it }
-
-    internal fun write(buf: RustBufferBuilder) {
-        // The Rust code always expects pointers written as 8 bytes,
-        // and will fail to compile if they don't fit.
-        buf.putLong(Pointer.nativeValue(this.lower()))
-    }
-
-    override fun getNewAddress(): String =
-        callWithPointer {
-    rustCall() { status ->
-    _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_get_new_address(it,  status)
-}
-        }.let {
-            String.lift(it)
-        }
-    
-    override fun getBalance(): ULong =
-        callWithPointer {
-    rustCallWithError(BdkException) { status ->
-    _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_get_balance(it,  status)
-}
-        }.let {
-            ULong.lift(it)
-        }
-    
-    override fun sign(psbt: PartiallySignedBitcoinTransaction ) =
-        callWithPointer {
-    rustCallWithError(BdkException) { status ->
-    _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_sign(it, psbt.lower() , status)
-}
-        }
-    
-    override fun getTransactions(): List<ConfirmedTransaction> =
-        callWithPointer {
-    rustCallWithError(BdkException) { status ->
-    _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_get_transactions(it,  status)
-}
-        }.let {
-            liftSequenceTypeConfirmedTransaction(it)
-        }
-    
-    override fun getNetwork(): Network =
-        callWithPointer {
-    rustCall() { status ->
-    _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_get_network(it,  status)
-}
-        }.let {
-            Network.lift(it)
-        }
-    
-    override fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) =
-        callWithPointer {
-    rustCallWithError(BdkException) { status ->
-    _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status)
-}
-        }
-    
-    override fun broadcast(psbt: PartiallySignedBitcoinTransaction ): String =
-        callWithPointer {
-    rustCallWithError(BdkException) { status ->
-    _UniFFILib.INSTANCE.bdk_1945_OnlineWallet_broadcast(it, psbt.lower() , status)
-}
-        }.let {
-            String.lift(it)
-        }
-    
-    
-
-    companion object {
-        internal fun lift(ptr: Pointer): OnlineWallet {
-            return OnlineWallet(ptr)
-        }
-
-        internal fun read(buf: ByteBuffer): OnlineWallet {
-            // The Rust code always writes pointers as 8 bytes, and will
-            // fail to compile if they don't fit.
-            return OnlineWallet.lift(Pointer(buf.getLong()))
-        }
-
-        
-    }
-}
-
-@ExperimentalUnsignedTypes
-public interface PartiallySignedBitcoinTransactionInterface {
-    
-}
-
-@ExperimentalUnsignedTypes
-class PartiallySignedBitcoinTransaction(
-    pointer: Pointer
-) : FFIObject(pointer), PartiallySignedBitcoinTransactionInterface {
-    constructor(wallet: OnlineWallet, recipient: String, amount: ULong ) :
-        this(
-    rustCallWithError(BdkException) { status ->
-    _UniFFILib.INSTANCE.bdk_1945_PartiallySignedBitcoinTransaction_new(wallet.lower(), recipient.lower(), amount.lower() ,status)
-})
-
-    /**
-     * Disconnect the object from the underlying Rust object.
-     * 
-     * It can be called more than once, but once called, interacting with the object
-     * causes an `IllegalStateException`.
-     * 
-     * Clients **must** call this method once done with the object, or cause a memory leak.
-     */
-    override protected fun freeRustArcPtr() {
-        rustCall() { status ->
-            _UniFFILib.INSTANCE.ffi_bdk_1945_PartiallySignedBitcoinTransaction_object_free(this.pointer, status)
-        }
-    }
-
-    internal fun lower(): Pointer = callWithPointer { it }
-
-    internal fun write(buf: RustBufferBuilder) {
-        // The Rust code always expects pointers written as 8 bytes,
-        // and will fail to compile if they don't fit.
-        buf.putLong(Pointer.nativeValue(this.lower()))
-    }
-
-    
-
-    companion object {
-        internal fun lift(ptr: Pointer): PartiallySignedBitcoinTransaction {
-            return PartiallySignedBitcoinTransaction(ptr)
-        }
-
-        internal fun read(buf: ByteBuffer): PartiallySignedBitcoinTransaction {
-            // The Rust code always writes pointers as 8 bytes, and will
-            // fail to compile if they don't fit.
-            return PartiallySignedBitcoinTransaction.lift(Pointer(buf.getLong()))
-        }
-
-        
-    }
-}
-
-
-// Callback Interfaces
-
-
-public interface BdkProgress {
-    fun update(progress: Float, message: String? )
-    
-}
-
-
-internal class CallbackInterfaceBdkProgressFFI : ForeignCallback {
-    @Suppress("TooGenericExceptionCaught")
-    override fun invoke(handle: Long, method: Int, args: RustBuffer.ByValue): RustBuffer.ByValue {
-        return CallbackInterfaceBdkProgressInternals.handleMap.callWithResult(handle) { cb -> 
-            when (method) {
-                IDX_CALLBACK_FREE -> CallbackInterfaceBdkProgressInternals.drop(handle)
-                1 -> this.invokeUpdate(cb, args)
-                
-                // This should never happen, because an out of bounds method index won't
-                // ever be used. Once we can catch errors, we should return an InternalException.
-                // https://github.com/mozilla/uniffi-rs/issues/351
-                else -> RustBuffer.ByValue()
-            }
-        }
-    }
-
-    
-    private fun invokeUpdate(kotlinCallbackInterface: BdkProgress, args: RustBuffer.ByValue): RustBuffer.ByValue =
-        try {
-            val buf = args.asByteBuffer() ?: throw InternalException("No ByteBuffer in RustBuffer; this is a Uniffi bug")
-            kotlinCallbackInterface.update(
-                    Float.read(buf), 
-                    readOptionalstring(buf) 
-                    )
-            .let { RustBuffer.ByValue() }
-                // TODO catch errors and report them back to Rust. 
-                // https://github.com/mozilla/uniffi-rs/issues/351
-        } finally {
-            RustBuffer.free(args)
-        }
-
-    
-}
-
-internal object CallbackInterfaceBdkProgressInternals: CallbackInternals<BdkProgress>(
-    foreignCallback = CallbackInterfaceBdkProgressFFI()
-) {
-    override fun register(lib: _UniFFILib) {
-        rustCall() { status ->
-            lib.ffi_bdk_1945_BdkProgress_init_callback(this.foreignCallback, status)
-        }
-    }
-}
-
-
diff --git a/bindings/bdk-kotlin/settings.gradle b/bindings/bdk-kotlin/settings.gradle
index ac610bec..4c3c9127 100644
--- a/bindings/bdk-kotlin/settings.gradle
+++ b/bindings/bdk-kotlin/settings.gradle
@@ -1,4 +1,4 @@
 rootProject.name = 'bdk-kotlin'
 
-include ':jvm', ':demo' //, ':android', ':test-fixtures'
+include ':jvm',':demo',':test-fixtures',':android'
 
diff --git a/build.sh b/build.sh
index b67fff0c..d94e956d 100755
--- a/build.sh
+++ b/build.sh
@@ -32,13 +32,11 @@ copy_lib_kotlin() {
       echo -n "darwin "
       mkdir -p bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64
       cp target/debug/libuniffi_bdk.dylib bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64
-      cp target/debug/libuniffi_bdk.dylib bindings/bdk-kotlin/demo/src/main/resources/darwin-x86-64
       ;;
     "Linux")
       echo -n "linux "
       mkdir -p bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64
       cp target/debug/libuniffi_bdk.so bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64
-      cp target/debug/libuniffi_bdk.so bindings/bdk-kotlin/demo/src/main/resources/linux-x86-64
       ;;
   esac
   echo "libs to kotlin sub-project"
@@ -51,6 +49,8 @@ build_kotlin() {
 
 ## rust android
 build_android() {
+  build_kotlin
+
   # If ANDROID_NDK_HOME is not set then set it to github actions default
   [ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle
 
@@ -63,27 +63,27 @@ build_android() {
   # IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on
   BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}"
 
-  mkdir -p bdk-kotlin/android/src/main/jniLibs/ bdk-kotlin/android/src/main/jniLibs/arm64-v8a bdk-kotlin/android/src/main/jniLibs/x86_64 bdk-kotlin/android/src/main/jniLibs/armeabi-v7a bdk-kotlin/android/src/main/jniLibs/x86
+  mkdir -p bindings/bdk-kotlin/android/src/main/jniLibs/ bindings/bdk-kotlin/android/src/main/jniLibs/arm64-v8a bindings/bdk-kotlin/android/src/main/jniLibs/x86_64 bindings/bdk-kotlin/android/src/main/jniLibs/armeabi-v7a bindings/bdk-kotlin/android/src/main/jniLibs/x86
 
   if echo $BUILD_TARGETS | grep "aarch64"; then
       CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android
-      cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/arm64-v8a
+      cp target/aarch64-linux-android/debug/libuniffi_bdk.so bindings/bdk-kotlin/android/src/main/jniLibs/arm64-v8a
   fi
   if echo $BUILD_TARGETS | grep "x86_64"; then
       CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android
-      cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86_64
+      cp target/x86_64-linux-android/debug/libuniffi_bdk.so bindings/bdk-kotlin/android/src/main/jniLibs/x86_64
   fi
   if echo $BUILD_TARGETS | grep "armv7"; then
       CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi
-      cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/armeabi-v7a
+      cp target/armv7-linux-androideabi/debug/libuniffi_bdk.so bindings/bdk-kotlin/android/src/main/jniLibs/armeabi-v7a
   fi
   if echo $BUILD_TARGETS | grep "i686"; then
       CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android
-      cp target/i686-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86
+      cp target/i686-linux-android/debug/libuniffi_bdk.so bindings/bdk-kotlin/android/src/main/jniLibs/x86
   fi
 
   # bdk-kotlin aar
-  (cd bdk-kotlin && ./gradlew :android:build && ./gradlew :android:publishToMavenLocal)
+  (cd bindings/bdk-kotlin && ./gradlew :android:build && ./gradlew :android:publishToMavenLocal)
 }
 
 OS=$(uname)
@@ -97,6 +97,7 @@ else
 
   while [ -n "$1" ]; do # while loop starts
     case "$1" in
+      -a) build_android ;;
       -k) build_kotlin ;;
       -h) help ;;
       *) echo "Option $1 not recognized" ;;


Test patch:
diff --git a/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/bdk/JvmLibTest.kt b/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/bdk/JvmLibTest.kt
new file mode 100644
index 00000000..ceea526c
--- /dev/null
+++ b/bindings/bdk-kotlin/jvm/src/test/kotlin/org/bitcoindevkit/bdk/JvmLibTest.kt
@@ -0,0 +1,16 @@
+package org.bitcoindevkit.bdk
+
+import java.nio.file.Files
+
+/**
+ * Library test, which will execute on linux host.
+ *
+ */
+class JvmLibTest : LibTest() {
+
+    override fun getTestDataDir(): String {
+        return Files.createTempDirectory("bdk-test").toString()
+        //return Paths.get(System.getProperty("java.io.tmpdir"), "bdk-test").toString()
+    }
+
+}
diff --git a/bindings/bdk-kotlin/test-fixtures/build.gradle b/bindings/bdk-kotlin/test-fixtures/build.gradle
new file mode 100644
index 00000000..44f2c40b
--- /dev/null
+++ b/bindings/bdk-kotlin/test-fixtures/build.gradle
@@ -0,0 +1,18 @@
+plugins {
+    id 'org.jetbrains.kotlin.jvm'
+    id 'java-library'
+}
+
+java {
+    sourceCompatibility = JavaVersion.VERSION_1_8
+    targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+dependencies {
+    implementation platform('org.jetbrains.kotlin:kotlin-bom')
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+
+    implementation(project(':jvm'))
+    implementation "junit:junit:4.13.2"
+    api "org.slf4j:slf4j-api:1.7.30"
+}
diff --git a/bindings/bdk-kotlin/jvm/src/test/kotlin/uniffi/bdk/LibTest.kt b/bindings/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt
similarity index 70%
rename from bindings/bdk-kotlin/jvm/src/test/kotlin/uniffi/bdk/LibTest.kt
rename to bindings/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt
index 6e8c7b15..1ac682f7 100644
--- a/bindings/bdk-kotlin/jvm/src/test/kotlin/uniffi/bdk/LibTest.kt
+++ b/bindings/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt
@@ -1,19 +1,24 @@
-package uniffi.bdk
+package org.bitcoindevkit.bdk
 
-import uniffi.bdk.OfflineWallet
 import org.junit.Assert.*
 import org.junit.Test
-
-class LogProgress: BdkProgress {
-    override fun update(progress: Float, message: String? ) {
-        println("progress: $progress, message: $message")
-    }
-}
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import uniffi.bdk.*
+import java.io.File
 
 /**
  * Library tests which will execute for jvm and android modules.
  */
-class LibTest {
+abstract class LibTest {
+
+    abstract fun getTestDataDir(): String
+
+    fun cleanupTestDataDir(testDataDir: String) {
+        File(testDataDir).deleteRecursively()
+    }
+
+    val log: Logger = LoggerFactory.getLogger(LibTest::class.java)
 
     val desc =
         "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
@@ -27,7 +32,7 @@ class LibTest {
         assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs")
     }
 
-    @Test(expected=BdkException.Descriptor::class)
+    @Test(expected= BdkException.Descriptor::class)
     fun invalidDescriptorExceptionIsThrown() {
         val config = DatabaseConfig.Memory("")
         OfflineWallet("invalid-descriptor", Network.REGTEST, config)
@@ -35,11 +40,13 @@ class LibTest {
 
     @Test
     fun sledWalletNewAddress() {
-        val config = DatabaseConfig.Sled(SledDbConfiguration("/tmp/testdb", "testdb"))
+        val testDataDir = getTestDataDir()
+        val config = DatabaseConfig.Sled(SledDbConfiguration(testDataDir, "testdb"))
         val wallet = OfflineWallet(desc, Network.REGTEST, config)
         val address = wallet.getNewAddress()
         assertNotNull(address)
         assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs")
+        cleanupTestDataDir(testDataDir)
     }
 
     @Test
@@ -52,6 +59,14 @@ class LibTest {
         assertEquals(network, Network.TESTNET)
     }
 
+    class LogProgress: BdkProgress {
+        val log: Logger = LoggerFactory.getLogger(LibTest::class.java)
+
+        override fun update(progress: Float, message: String?) {
+            log.debug("Syncing...")
+        }
+    }
+
     @Test
     fun onlineWalletSyncGetBalance() {
         val db = DatabaseConfig.Memory("")
diff --git a/test.sh b/test.sh
index e46caed2..e552176f 100755
--- a/test.sh
+++ b/test.sh
@@ -11,13 +11,18 @@ help()
    echo
    echo "Syntax: build [-a|h|k|v]"
    echo "options:"
+   echo "-a     Android connected device tests."
    echo "-h     Print this Help."
    echo "-k     Kotlin tests."
    echo
 }
 
 test_kotlin() {
-  (cd bindings/bdk-kotlin && rm -rf /tmp/testdb && ./gradlew test -Djna.debug_load=true)
+  (cd bindings/bdk-kotlin && ./gradlew :jvm:test -Djna.debug_load=true)
+}
+
+test_android() {
+  (cd bindings/bdk-kotlin && ./gradlew :android:connectedDebugAndroidTest)
 }
 
 if [ $1 = "-h" ]
@@ -29,6 +34,7 @@ else
   # optional tests
   while [ -n "$1" ]; do # while loop starts
     case "$1" in
+      -a) test_android ;;
       -h) help ;;
       -k) test_kotlin ;;
       *) echo "Option $1 not recognized" ;;