Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/kotlin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Build and Test Kotlin
on:
workflow_run:
workflows: ["Flake maintenance"]
types: [requested]
branches:
- "update_flake_lock_action"
pull_request:
paths:
- payjoin-ffi/**
# The jobs run inside the flake's kotlin dev shell, so changes to
# the flake change this workflow's environment.
- flake.nix
- flake.lock
- .github/workflows/kotlin.yml

jobs:
build-kotlin-and-test:
name: "Build and test kotlin"
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-26.04, macos-latest]
env:
RUSTUP_TOOLCHAIN: 1.85.0
steps:
- name: Checkout
uses: actions/checkout@v6
- name: "Install Rust 1.85.0"
uses: dtolnay/rust-toolchain@1.85.0
- name: "Use cache"
uses: Swatinem/rust-cache@v2
with:
shared-key: msrv-workspace
- name: Set up nix
uses: ./.github/actions/setup-nix
- name: "Build and test"
run: nix develop .#kotlin -c bash ./payjoin-ffi/kotlin/contrib/test.sh
19 changes: 19 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,24 @@
BITCOIND_SKIP_DOWNLOAD = 1;
};

kotlinDevShell = pkgs.mkShell {
name = "kotlin-dev";
packages =
with pkgs;
[
rustVersions.msrv
jdk21
bzip2
]
++ lib.optionals pkgs.stdenv.isLinux [
pkg-config
openssl
clang
];
BITCOIND_EXE = pkgs.lib.getExe' pkgs.bitcoind "bitcoind";
BITCOIND_SKIP_DOWNLOAD = 1;
};

# Rust toolchain for the python dev shell: msrv pinned to match
# payjoin-ffi/python build requirements, with per-arch targets added
# so cargo can build artifacts under nix for payjoin-ffi/python/scripts/generate_bindings.sh
Expand Down Expand Up @@ -502,6 +520,7 @@
javascript = javascriptDevShell;
csharp = csharpDevShell;
dart = dartDevShell;
kotlin = kotlinDevShell;
};
formatter = treefmtEval.config.build.wrapper;
checks =
Expand Down
1 change: 1 addition & 0 deletions payjoin-ffi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The directories below include instructions for using, building, and publishing t
| Dart | linux, macOS | [payjoin-ffi/dart](dart) | [pub.dev](https://pub.dev/packages/payjoin) |
| JavaScript | linux, macOS | [payjoin-ffi/javascript](javascript) | [npm](https://www.npmjs.com/package/payjoin) |
| C# | linux, macOS, windows | [payjoin-ffi/csharp](csharp) | [nuget](https://www.nuget.org/packages/Payjoin) |
| Kotlin | linux, macOS | [payjoin-ffi/kotlin](kotlin) | (not published) |

## Minimum Supported Rust Version (MSRV)

Expand Down
2 changes: 1 addition & 1 deletion payjoin-ffi/contrib/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set -e
cd "$(dirname "$0")/.."
cargo test --package payjoin-ffi --verbose --features=_manual-tls,_test-utils
BINDINGS="dart javascript python csharp"
BINDINGS="dart javascript python csharp kotlin"
pids=()
tmpfiles=()
for binding in $BINDINGS; do
Expand Down
15 changes: 15 additions & 0 deletions payjoin-ffi/kotlin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Generated UniFFI Kotlin
src/main/kotlin/org/

# Native library copied by generate_bindings.sh
/lib/*.so
/lib/*.dylib
/lib/*.dll

# Gradle
.gradle/
build/
local.properties

.idea/
.DS_Store
29 changes: 29 additions & 0 deletions payjoin-ffi/kotlin/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Contributing to the Payjoin Kotlin Bindings

Kotlin/JVM bindings for the [Payjoin Dev Kit](https://payjoindevkit.org/), generated from
`payjoin-ffi` with Mozilla UniFFI. This document covers building from source and running tests.

## Development

```shell
git clone https://github.com/payjoin/rust-payjoin.git
cd rust-payjoin/payjoin-ffi/kotlin
bash ./scripts/generate_bindings.sh
./gradlew test
```

Generation uses the in-tree `uniffi-bindgen` binary (`--language kotlin`). There is no extra Cargo
feature on `payjoin-ffi`. By default, development generation enables `_test-utils`. For production
bindings, set `PAYJOIN_FFI_FEATURES` to empty:

```shell
PAYJOIN_FFI_FEATURES= bash ./scripts/generate_bindings.sh
```

Protocol `close` is renamed to `closeSession` only in `[bindings.kotlin.rename]` in
`payjoin-ffi/uniffi.toml`, so it does not clash with `AutoCloseable.close()`.

With nix, `nix develop .#kotlin` provides the pinned MSRV Rust toolchain, JDK 21, and
`BITCOIND_EXE` (via `nixpkgs`, with `BITCOIND_SKIP_DOWNLOAD=1`), and is what CI uses. Without
nix, use a local JDK 21+ and the Gradle wrapper; `corepc-node` will download `bitcoind` on
first test run.
34 changes: 34 additions & 0 deletions payjoin-ffi/kotlin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Payjoin Kotlin Bindings

Kotlin/JVM bindings for the [Payjoin Dev Kit](https://payjoindevkit.org/), generated from `payjoin-ffi` with UniFFI (`--language kotlin`).

Payjoin lets the receiver contribute inputs to the sender's transaction. These bindings implement [BIP 78](https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki) and [BIP 77](https://github.com/bitcoin/bips/blob/master/bip-0077.md).

Requires **JDK 21+**. Native `payjoin_ffi` is loaded via JNA.

Protocol session teardown on pending-fallback and JSON persisters is `closeSession()`. `AutoCloseable.close()` drops the Rust handle (try-with-resources / `.use`). Other language bindings keep `close`.

## Build and test

```shell
cd payjoin-ffi/kotlin
bash ./scripts/generate_bindings.sh
./gradlew test
```

Or `bash ./contrib/test.sh` from this directory (uses `Cargo-recent.lock`).

`./gradlew test` includes a v2↔v2 integration test that starts a local payjoin directory, OHTTP relay, and bitcoind. Inside the nix `.#kotlin` shell, `BITCOIND_EXE` points at the nixpkgs
`bitcoind` and `BITCOIND_SKIP_DOWNLOAD=1` is set, so no download happens. Outside the nix shell,
Bitcoin Core is downloaded by corepc-node (`29_0`) on first run instead.

Without nix: Rust (see repo `rust-toolchain.toml` / MSRV 1.85), JDK 21+, and the Gradle wrapper in this directory. Network access is required the first time bitcoind is fetched.

## Stability

Pre-1.0. Generated sources are not committed; run generate before test or pack.

## Documentation

- [Payjoin Dev Kit](https://payjoindevkit.org/)
- [rust-payjoin](https://github.com/payjoin/rust-payjoin)
43 changes: 43 additions & 0 deletions payjoin-ffi/kotlin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
plugins {
// KGP's Gradle compatibility matrix must cover the wrapper version pinned in
// gradle/wrapper/gradle-wrapper.properties (currently 9.1.0). 2.1.20 only tests
// against Gradle up to 8.12.1; 2.3.20+ is the first stable KGP line whose matrix
// extends to 9.1.0 (tested up to 9.3.0).
kotlin("jvm") version "2.3.21"
}

repositories {
mavenCentral()
}

dependencies {
api("net.java.dev.jna:jna:5.17.0")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
testImplementation(kotlin("test"))
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1")
}

java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}

kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21)
}
}

tasks.test {
useJUnitPlatform()
val libDir = layout.projectDirectory.dir("lib").asFile

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add inputs.file(native) here. Otherwise changing the Rust library without changing the generated Kotlin leaves the tests UP-TO-DATE. Confirmed with an isolated Gradle 9.1 repro.

val native = listOf("libpayjoin_ffi.so", "libpayjoin_ffi.dylib", "payjoin_ffi.dll")
.map { libDir.resolve(it) }
.firstOrNull { it.exists() }
if (native != null) {
inputs.file(native)
systemProperty("uniffi.component.payjoin.libraryOverride", native.absolutePath)
}
systemProperty("jna.library.path", libDir.absolutePath)
}
15 changes: 15 additions & 0 deletions payjoin-ffi/kotlin/contrib/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
cd "$REPO_ROOT"
source contrib/lockfile.sh
use_lockfile Cargo-recent.lock

cd "$REPO_ROOT/payjoin-ffi/kotlin"

echo "==> Generating FFI bindings..."
bash ./scripts/generate_bindings.sh

echo "==> Running Kotlin tests..."
./gradlew --no-daemon test
2 changes: 2 additions & 0 deletions payjoin-ffi/kotlin/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx1g
kotlin.code.style=official
Binary file not shown.
8 changes: 8 additions & 0 deletions payjoin-ffi/kotlin/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
distributionSha256Sum=a17ddd85a26b6a7f5ddb71ff8b05fc5104c0202c6e64782429790c933686c806
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading