Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
42 changes: 42 additions & 0 deletions .github/workflows/kotlin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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
Comment thread
ram0verflow marked this conversation as resolved.
- name: "Compile production bindings"
run: |
nix develop .#kotlin -c env PAYJOIN_FFI_FEATURES= bash ./payjoin-ffi/kotlin/scripts/generate_bindings.sh
nix develop .#kotlin -c bash -c 'cd payjoin-ffi/kotlin && ./gradlew --no-daemon compileKotlin'
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)
47 changes: 47 additions & 0 deletions payjoin-ffi/kotlin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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
Comment thread
ram0verflow marked this conversation as resolved.
val os = System.getProperty("os.name").lowercase()
val nativeName = when {
os.contains("mac") || os.contains("darwin") -> "libpayjoin_ffi.dylib"
os.contains("win") -> "payjoin_ffi.dll"
else -> "libpayjoin_ffi.so"
}
val native = libDir.resolve(nativeName).takeIf { 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