diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 0f36985..226342c 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -7,6 +7,23 @@ jobs:
test:
name: "Build library and run unit tests"
runs-on: ubuntu-24.04
+
+ services:
+ bitcoin-regtest:
+ image: ghcr.io/thunderbiscuit/podman-regtest-infinity-pro:0.3.2
+ ports:
+ - 18443:18443
+ - 18444:18444
+ - 3002:3002
+ - 3003:3003
+ - 60401:60401
+ # The container initialization step will wait until everything is ready before allowing other steps to run
+ options: >-
+ --health-cmd "test $(bitcoin-cli -regtest -rpcuser=regtest -rpcpassword=password getblockcount) -gt 0"
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 6
+
steps:
- name: "Check out PR branch"
uses: actions/checkout@v4
diff --git a/bdk-ffi b/bdk-ffi
index 08ec19b..ecfc0b9 160000
--- a/bdk-ffi
+++ b/bdk-ffi
@@ -1 +1 @@
-Subproject commit 08ec19b9bfb5cbc2c1fb6d003c58a25b01fa25e6
+Subproject commit ecfc0b9ca68d01d503db78488381a066696a9be6
diff --git a/build.gradle.kts b/build.gradle.kts
index 098a469..9d90651 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,13 +1,5 @@
plugins {
- id("org.jetbrains.kotlin.jvm").version("2.1.10").apply(false)
- id("org.gradle.java-library")
- id("org.gradle.maven-publish")
- id("org.gradle.signing")
- id("org.jetbrains.dokka").version("2.0.0").apply(false)
- id("org.jetbrains.dokka-javadoc").version("2.0.0").apply(false)
+ id("org.jetbrains.kotlin.jvm").version("2.2.0").apply(false)
+ id("org.jetbrains.dokka").version("2.1.0").apply(false)
+ id("com.vanniktech.maven.publish").version("0.36.0").apply(false)
}
-
-// library version is defined in gradle.properties
-val libraryVersion: String by project
-group = "org.bitcoindevkit"
-version = libraryVersion
diff --git a/lib/README.md b/docs/DOKKA_LANDING.md
similarity index 92%
rename from lib/README.md
rename to docs/DOKKA_LANDING.md
index 1d28d65..2b9e751 100644
--- a/lib/README.md
+++ b/docs/DOKKA_LANDING.md
@@ -1,7 +1,9 @@
# Module bdk-jvm
-The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for Kotlin on the JVM.
+The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for Kotlin on the JVM.
-# Package org.bitcoindevkit
+
The functionality exposed in this package is in fact a combination of the [bdk_wallet](https://crates.io/crates/bdk_wallet), [bdk_core](https://crates.io/crates/bdk_core), [bdk_electrum](https://crates.io/crates/bdk_electrum), [bdk_esplora](https://crates.io/crates/bdk_esplora), [bitcoin](https://crates.io/crates/bitcoin), and [miniscript](https://crates.io/crates/miniscript) crates.
+
+# Package org.bitcoindevkit
diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts
index ce3ac04..9104e7a 100644
--- a/examples/build.gradle.kts
+++ b/examples/build.gradle.kts
@@ -1,8 +1,8 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
- id("org.jetbrains.kotlin.jvm") version "2.1.10"
- id("application")
+ id("org.jetbrains.kotlin.jvm")
}
repositories {
@@ -11,16 +11,11 @@ repositories {
dependencies {
implementation(project(":lib"))
- testImplementation(kotlin("test"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
implementation("org.slf4j:slf4j-api:2.0.16")
implementation("ch.qos.logback:logback-classic:1.5.13")
}
-tasks.test {
- useJUnitPlatform()
-}
-
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
@@ -28,7 +23,7 @@ java {
withJavadocJar()
}
-tasks.withType {
+tasks.withType {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
diff --git a/gradle.properties b/gradle.properties
index 50fb5a3..03d74fb 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,6 +1,5 @@
org.gradle.jvmargs=-Xmx1536m
android.enableJetifier=true
kotlin.code.style=official
-libraryVersion=2.3.0
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 7f93135..e644113 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index e7646de..5f38436 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index 0adc8e1..1aa94a4 100755
--- a/gradlew
+++ b/gradlew
@@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
- # shellcheck disable=SC3045
+ # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
- # shellcheck disable=SC3045
+ # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -202,11 +202,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
-# Collect all arguments for the java command;
-# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-# shell script including quotes and variable substitutions, so put them in
-# double quotes to make sure that they get re-expanded; and
-# * put everything else in single quotes, so that it's not re-expanded.
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
diff --git a/gradlew.bat b/gradlew.bat
index 93e3f59..25da30d 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
diff --git a/justfile b/justfile
index 6eaa68e..20a1028 100644
--- a/justfile
+++ b/justfile
@@ -16,7 +16,7 @@ docs:
[group("Repo")]
[doc("Publish the library to your local Maven repository.")]
publish-local:
- ./gradlew publishToMavenLocal -P localBuild
+ ./gradlew publishToMavenLocal
[group("Submodule")]
[doc("Initialize bdk-ffi submodule to committed hash.")]
@@ -58,6 +58,8 @@ clean:
rm -rf ./lib/build/
rm -rf ./examples/build/
rm -rf ./examples/data/
+ rm -rf ./lib/src/main/kotlin/org/bitcoindevkit/*
+ rm -rf ./lib/src/main/resources/*
[group("Test")]
[doc("Run all tests, unless a specific test is provided.")]
diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts
index 546245a..0c9d927 100644
--- a/lib/build.gradle.kts
+++ b/lib/build.gradle.kts
@@ -1,44 +1,37 @@
-import org.gradle.api.tasks.testing.logging.TestExceptionFormat.*
-import org.gradle.api.tasks.testing.logging.TestLogEvent.*
+import com.vanniktech.maven.publish.JavadocJar
+import com.vanniktech.maven.publish.KotlinJvm
+import com.vanniktech.maven.publish.SourcesJar
+import org.gradle.api.tasks.testing.logging.TestExceptionFormat
+import org.gradle.api.tasks.testing.logging.TestLogEvent
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-// library version is defined in gradle.properties
-val libraryVersion: String by project
-
plugins {
id("org.jetbrains.kotlin.jvm")
- id("org.gradle.java-library")
- id("org.gradle.maven-publish")
- id("org.gradle.signing")
+ id("com.vanniktech.maven.publish")
id("org.jetbrains.dokka")
- id("org.jetbrains.dokka-javadoc")
}
+group = "org.bitcoindevkit"
+version = "2.3.0-SNAPSHOT"
+
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
- withSourcesJar()
- withJavadocJar()
}
-tasks.withType {
- kotlinOptions {
- jvmTarget = "11"
+tasks.withType {
+ compilerOptions {
+ jvmTarget.set(JvmTarget.JVM_11)
}
}
-testing {
- suites {
- val test by getting(JvmTestSuite::class) {
- useKotlinTest("1.9.23")
- }
- }
-}
+tasks.test {
+ useJUnitPlatform()
-tasks.withType {
testLogging {
- events(PASSED, SKIPPED, FAILED, STANDARD_OUT, STANDARD_ERROR)
- exceptionFormat = FULL
+ events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED, TestLogEvent.STANDARD_OUT)
+ exceptionFormat = TestExceptionFormat.FULL
showExceptions = true
showStackTraces = true
showCauses = true
@@ -46,63 +39,69 @@ tasks.withType {
}
dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
+ // JNA
implementation("net.java.dev.jna:jna:5.14.0")
+
+ // Coroutines
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
+
+ // Tests
+ testImplementation(kotlin("test"))
+ testImplementation("org.kotlinbitcointools:regtest-toolbox:0.1.0")
}
-afterEvaluate {
- publishing {
- publications {
- create("maven") {
- groupId = "org.bitcoindevkit"
- artifactId = "bdk-jvm"
- version = libraryVersion
+mavenPublishing {
+ coordinates(
+ groupId = group.toString(),
+ artifactId = "bdk-jvm",
+ version = version.toString()
+ )
- from(components["java"])
- pom {
- name.set("bdk-jvm")
- description.set("Bitcoin Dev Kit Kotlin language bindings.")
- url.set("https://bitcoindevkit.org")
- licenses {
- license {
- name.set("APACHE 2.0")
- url.set("https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-APACHE")
- }
- license {
- name.set("MIT")
- url.set("https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-MIT")
- }
- }
- developers {
- developer {
- id.set("bdkdevelopers")
- name.set("Bitcoin Dev Kit Developers")
- email.set("dev@bitcoindevkit.org")
- }
- }
- scm {
- connection.set("scm:git:github.com/bitcoindevkit/bdk-ffi.git")
- developerConnection.set("scm:git:ssh://github.com/bitcoindevkit/bdk-ffi.git")
- url.set("https://github.com/bitcoindevkit/bdk-ffi/tree/master")
- }
- }
+ pom {
+ name.set("bdk-jvm")
+ description.set("Bitcoin Dev Kit Kotlin language bindings.")
+ url.set("https://bitcoindevkit.org")
+ inceptionYear.set("2021")
+ licenses {
+ license {
+ name.set("APACHE 2.0")
+ url.set("https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-APACHE")
+ }
+ license {
+ name.set("MIT")
+ url.set("https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-MIT")
+ }
+ }
+ developers {
+ developer {
+ id.set("bdkdevelopers")
+ name.set("Bitcoin Dev Kit Developers")
+ email.set("dev@bitcoindevkit.org")
}
}
+ scm {
+ url.set("https://github.com/bitcoindevkit/bdk-ffi/")
+ connection.set("scm:git:github.com/bitcoindevkit/bdk-ffi.git")
+ developerConnection.set("scm:git:ssh://github.com/bitcoindevkit/bdk-ffi.git")
+ }
}
-}
-signing {
- if (project.hasProperty("localBuild")) {
- isRequired = false
- }
- sign(publishing.publications)
+ configure(
+ KotlinJvm(
+ javadocJar = JavadocJar.Dokka("dokkaGeneratePublicationHtml"),
+ sourcesJar = SourcesJar.Sources(),
+ )
+ )
+
+ publishToMavenCentral()
+ signAllPublications()
}
dokka {
moduleName.set("bdk-jvm")
- moduleVersion.set(libraryVersion)
+ moduleVersion.set(version.toString())
dokkaSourceSets.main {
- includes.from("README.md")
+ includes.from("../docs/DOKKA_LANDING.md")
sourceLink {
localDirectory.set(file("src/main/kotlin"))
remoteUrl("https://bitcoindevkit.org/")
diff --git a/lib/src/test/kotlin/org/bitcoindevkit/CbfSyncTest.kt b/lib/src/test/kotlin/org/bitcoindevkit/CbfSyncTest.kt
new file mode 100644
index 0000000..0ea6c0f
--- /dev/null
+++ b/lib/src/test/kotlin/org/bitcoindevkit/CbfSyncTest.kt
@@ -0,0 +1,55 @@
+package org.bitcoindevkit
+
+import org.junit.jupiter.api.Nested
+import org.kotlinbitcointools.regtesttoolbox.regenv.RegEnv
+import kotlinx.coroutines.runBlocking
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
+import kotlin.test.Test
+
+class CbfSyncTest {
+ private val conn: Persister = Persister.newInMemory()
+
+ @Nested
+ inner class Success {
+ @Test
+ fun `Successful sync using a Kyoto client`() {
+ runBlocking {
+ val regtestEnv = RegEnv.connectTo(walletName = "faucet", username = "regtest", password = "password")
+
+ val wallet: Wallet = Wallet.createSingle(
+ descriptor = TEST_BIP86_DESCRIPTOR,
+ network = Network.REGTEST,
+ persister = conn
+ )
+ val newAddress = wallet.revealNextAddress(KeychainKind.EXTERNAL).address
+
+ regtestEnv.send(newAddress.toString(), 0.12345678, 2.0)
+ regtestEnv.mine(2)
+
+ val persistenceDir: Path = Paths.get("src/test/data").toAbsolutePath().normalize()
+ Files.createDirectories(persistenceDir)
+ val persistenceFilePath: Path = Files.createTempDirectory(persistenceDir, "kyoto_data_")
+
+ val ip: IpAddress = IpAddress.fromIpv4(127u, 0u, 0u, 1u)
+ val peer1: Peer = Peer(ip, 18444u, false)
+ val peers: List = listOf(peer1)
+ val (client, node) = CbfBuilder()
+ .peers(peers)
+ .connections(1u)
+ .scanType(ScanType.Sync)
+ .dataDir(persistenceFilePath.toString())
+ .build(wallet)
+
+ node.run()
+ val update: Update = client.update()
+ wallet.applyUpdate(update)
+
+ val balance = wallet.balance().total.toSat()
+ assert(balance > 0uL)
+ client.shutdown()
+ }
+ }
+ }
+}
\ No newline at end of file