diff --git a/.gitignore b/.gitignore
index 088ba6ba7..29c34dbec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,5 @@ Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
+
+*.dylib
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 000000000..26d33521a
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 000000000..1d9864d6f
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 000000000..a55e7a179
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/ldk-node.iml b/.idea/ldk-node.iml
new file mode 100644
index 000000000..9b4cf845b
--- /dev/null
+++ b/.idea/ldk-node.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 000000000..5a0adcddb
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 000000000..830674470
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 70026985e..3acdf312a 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,30 @@
# ldk-lite
A Simplified API for LDK.
+
+## Build and publish to local Maven repository
+```shell
+source uniffi_bindgen_generate_kotlin.sh
+./gradlew publishToMavenLocal
+```
+
+## How to Use
+To use the Kotlin language bindings for [`ldk-node`] in your JVM project, add the following to your gradle dependencies:
+```kotlin
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation("org.ldk:ldk-node:0.0.1")
+}
+```
+
+You may then import and use the `org.ldk_node` library in your Kotlin code. For example:
+```kotlin
+import uniffi.ldk_node.Builder
+import uniffi.ldk_node.Node
+
+fun main() {
+ val node: Node = Builder().build()
+}
+```
diff --git a/ldk-node-jvm/.gitattributes b/ldk-node-jvm/.gitattributes
new file mode 100644
index 000000000..00a51aff5
--- /dev/null
+++ b/ldk-node-jvm/.gitattributes
@@ -0,0 +1,6 @@
+#
+# https://help.github.com/articles/dealing-with-line-endings/
+#
+# These are explicitly windows files and should use crlf
+*.bat text eol=crlf
+
diff --git a/ldk-node-jvm/.gitignore b/ldk-node-jvm/.gitignore
new file mode 100644
index 000000000..67c4583a2
--- /dev/null
+++ b/ldk-node-jvm/.gitignore
@@ -0,0 +1,7 @@
+# Ignore Gradle project-specific cache directory
+.gradle
+
+# Ignore Gradle build output directory
+build
+
+.idea/
diff --git a/ldk-node-jvm/gradle.properties b/ldk-node-jvm/gradle.properties
new file mode 100644
index 000000000..a6c229201
--- /dev/null
+++ b/ldk-node-jvm/gradle.properties
@@ -0,0 +1 @@
+libraryVersion=0.0.1
diff --git a/ldk-node-jvm/gradle/wrapper/gradle-wrapper.jar b/ldk-node-jvm/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..41d9927a4
Binary files /dev/null and b/ldk-node-jvm/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/ldk-node-jvm/gradle/wrapper/gradle-wrapper.properties b/ldk-node-jvm/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..00e33edef
--- /dev/null
+++ b/ldk-node-jvm/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/ldk-node-jvm/gradlew b/ldk-node-jvm/gradlew
new file mode 100755
index 000000000..1b6c78733
--- /dev/null
+++ b/ldk-node-jvm/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# 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"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# 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.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/ldk-node-jvm/gradlew.bat b/ldk-node-jvm/gradlew.bat
new file mode 100644
index 000000000..107acd32c
--- /dev/null
+++ b/ldk-node-jvm/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "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.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+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.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/ldk-node-jvm/lib/build.gradle.kts b/ldk-node-jvm/lib/build.gradle.kts
new file mode 100644
index 000000000..2903e0367
--- /dev/null
+++ b/ldk-node-jvm/lib/build.gradle.kts
@@ -0,0 +1,100 @@
+import org.gradle.api.tasks.testing.logging.TestExceptionFormat.*
+import org.gradle.api.tasks.testing.logging.TestLogEvent.*
+
+// library version is defined in gradle.properties
+val libraryVersion: String by project
+
+plugins {
+ id("org.jetbrains.kotlin.jvm") version "1.5.31"
+ id("java-library")
+ id("maven-publish")
+}
+
+repositories {
+ mavenCentral()
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ withSourcesJar()
+ withJavadocJar()
+}
+
+tasks.withType {
+ useJUnitPlatform()
+
+ testLogging {
+ events(PASSED, SKIPPED, FAILED, STANDARD_OUT, STANDARD_ERROR)
+ exceptionFormat = FULL
+ showExceptions = true
+ showCauses = true
+ showStackTraces = true
+ }
+}
+
+dependencies {
+ // Align versions of all Kotlin components
+ implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
+
+ // Use the Kotlin JDK 8 standard library.
+ implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
+
+ implementation("net.java.dev.jna:jna:5.8.0")
+}
+
+afterEvaluate {
+ publishing {
+ publications {
+ create("maven") {
+ groupId = "org.ldk"
+ artifactId = "ldk-node"
+ version = libraryVersion
+
+ from(components["java"])
+ pom {
+ name.set("ldk-node")
+ description.set("Lightning Dev Kit Node language bindings for Kotlin.")
+ url.set("https://lightningdevkit.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("")
+ name.set("")
+ email.set("")
+ }
+ developer {
+ id.set("")
+ name.set("")
+ email.set("")
+ }
+ }
+ scm {
+ connection.set("scm:git:github.com/lightningdevkit/ldk-node.git")
+ developerConnection.set("scm:git:ssh://github.com/lightningdevkit/ldk-node.git")
+ url.set("https://github.com/lightningdevkit/ldk-node/tree/master")
+ }
+ }
+ }
+ }
+ }
+}
+
+testing {
+ suites {
+ // Configure the built-in test suite
+ val test by getting(JvmTestSuite::class) {
+ // Use Kotlin Test test framework
+ useKotlinTest()
+ }
+ }
+}
diff --git a/ldk-node-jvm/lib/src/main/kotlin/ldk/node/ldk_node.kt b/ldk-node-jvm/lib/src/main/kotlin/ldk/node/ldk_node.kt
new file mode 100644
index 000000000..56452d287
--- /dev/null
+++ b/ldk-node-jvm/lib/src/main/kotlin/ldk/node/ldk_node.kt
@@ -0,0 +1,1203 @@
+// 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.ldk_node;
+
+// 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 com.sun.jna.ptr.ByReference
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicLong
+
+// 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_ldk_node_ad4b_rustbuffer_alloc(size, status).also {
+ if(it.data == null) {
+ throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})")
+ }
+ }
+ }
+
+ internal fun free(buf: RustBuffer.ByValue) = rustCall() { status ->
+ _UniFFILib.INSTANCE.ffi_ldk_node_ad4b_rustbuffer_free(buf, status)
+ }
+ }
+
+ @Suppress("TooGenericExceptionThrown")
+ fun asByteBuffer() =
+ this.data?.getByteBuffer(0, this.len.toLong())?.also {
+ it.order(ByteOrder.BIG_ENDIAN)
+ }
+}
+
+/**
+ * The equivalent of the `*mut RustBuffer` type.
+ * Required for callbacks taking in an out pointer.
+ *
+ * Size is the sum of all values in the struct.
+ */
+class RustBufferByReference : ByReference(16) {
+ /**
+ * Set the pointed-to `RustBuffer` to the given value.
+ */
+ fun setValue(value: RustBuffer.ByValue) {
+ // NOTE: The offsets are as they are in the C-like struct.
+ val pointer = getPointer()
+ pointer.setInt(0, value.capacity)
+ pointer.setInt(4, value.len)
+ pointer.setPointer(8, value.data)
+ }
+}
+
+// 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
+}
+// The FfiConverter interface handles converter types to and from the FFI
+//
+// All implementing objects should be public to support external types. When a
+// type is external we need to import it's FfiConverter.
+public interface FfiConverter {
+ // Convert an FFI type to a Kotlin type
+ fun lift(value: FfiType): KotlinType
+
+ // Convert an Kotlin type to an FFI type
+ fun lower(value: KotlinType): FfiType
+
+ // Read a Kotlin type from a `ByteBuffer`
+ fun read(buf: ByteBuffer): KotlinType
+
+ // Calculate bytes to allocate when creating a `RustBuffer`
+ //
+ // This must return at least as many bytes as the write() function will
+ // write. It can return more bytes than needed, for example when writing
+ // Strings we can't know the exact bytes needed until we the UTF-8
+ // encoding, so we pessimistically allocate the largest size possible (3
+ // bytes per codepoint). Allocating extra bytes is not really a big deal
+ // because the `RustBuffer` is short-lived.
+ fun allocationSize(value: KotlinType): Int
+
+ // Write a Kotlin type to a `ByteBuffer`
+ fun write(value: KotlinType, buf: ByteBuffer)
+
+ // Lower a value into a `RustBuffer`
+ //
+ // This method lowers a value into a `RustBuffer` rather than the normal
+ // FfiType. It's used by the callback interface code. Callback interface
+ // returns are always serialized into a `RustBuffer` regardless of their
+ // normal FFI type.
+ fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue {
+ val rbuf = RustBuffer.alloc(allocationSize(value))
+ try {
+ val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity.toLong()).also {
+ it.order(ByteOrder.BIG_ENDIAN)
+ }
+ write(value, bbuf)
+ rbuf.writeField("len", bbuf.position())
+ return rbuf
+ } catch (e: Throwable) {
+ RustBuffer.free(rbuf)
+ throw e
+ }
+ }
+
+ // Lift a value from a `RustBuffer`.
+ //
+ // This here mostly because of the symmetry with `lowerIntoRustBuffer()`.
+ // It's currently only used by the `FfiConverterRustBuffer` class below.
+ fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType {
+ val byteBuf = rbuf.asByteBuffer()!!
+ try {
+ val item = read(byteBuf)
+ if (byteBuf.hasRemaining()) {
+ throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!")
+ }
+ return item
+ } finally {
+ RustBuffer.free(rbuf)
+ }
+ }
+}
+
+// FfiConverter that uses `RustBuffer` as the FfiType
+public interface FfiConverterRustBuffer: FfiConverter {
+ override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value)
+ override fun lower(value: KotlinType) = lowerIntoRustBuffer(value)
+}
+// 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.
+// Error runtime.
+@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 {
+ fun lift(error_buf: RustBuffer.ByValue): E;
+}
+
+// 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 rustCallWithError(errorHandler: CallStatusErrorHandler, 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(FfiConverterString.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 {
+ 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 rustCall(callback: (RustCallStatus) -> U): U {
+ return rustCallWithError(NullCallStatusErrorHandler, callback);
+}
+
+// Contains loading, initialization code,
+// and the FFI Function declarations in a com.sun.jna.Library.
+@Synchronized
+private fun findLibraryName(componentName: String): String {
+ val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride")
+ if (libOverride != null) {
+ return libOverride
+ }
+ return "uniffi_ldk_node"
+}
+
+private inline fun loadIndirect(
+ componentName: String
+): Lib {
+ return Native.load(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 = "ldk_node")
+
+ }
+ }
+
+ fun ffi_ldk_node_ad4b_Builder_object_free(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): Unit
+
+ fun ldk_node_ad4b_Builder_new(
+ _uniffi_out_err: RustCallStatus
+ ): Pointer
+
+ fun ldk_node_ad4b_Builder_build(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): Pointer
+
+ fun ffi_ldk_node_ad4b_Node_object_free(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): Unit
+
+ fun ldk_node_ad4b_Node_start(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): Unit
+
+ fun ldk_node_ad4b_Node_stop(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): Unit
+
+ fun ldk_node_ad4b_Node_next_event(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+ fun ldk_node_ad4b_Node_event_handled(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): Unit
+
+ fun ldk_node_ad4b_Node_node_id(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+ fun ldk_node_ad4b_Node_new_funding_address(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+ fun ldk_node_ad4b_Node_connect_open_channel(`ptr`: Pointer,`nodePubkeyAndAddress`: RustBuffer.ByValue,`channelAmountSats`: Long,`announceChannel`: Byte,
+ _uniffi_out_err: RustCallStatus
+ ): Unit
+
+ fun ldk_node_ad4b_Node_send_payment(`ptr`: Pointer,`invoice`: RustBuffer.ByValue,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+ fun ldk_node_ad4b_Node_send_spontaneous_payment(`ptr`: Pointer,`amountMsat`: Long,`nodeId`: RustBuffer.ByValue,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+ fun ldk_node_ad4b_Node_receive_payment(`ptr`: Pointer,`amountMsat`: RustBuffer.ByValue,`description`: RustBuffer.ByValue,`expirySecs`: Int,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+ fun ffi_ldk_node_ad4b_rustbuffer_alloc(`size`: Int,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+ fun ffi_ldk_node_ad4b_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+ fun ffi_ldk_node_ad4b_rustbuffer_free(`buf`: RustBuffer.ByValue,
+ _uniffi_out_err: RustCallStatus
+ ): Unit
+
+ fun ffi_ldk_node_ad4b_rustbuffer_reserve(`buf`: RustBuffer.ByValue,`additional`: Int,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+
+}
+
+// Public interface members begin here.
+
+
+public object FfiConverterUInt: FfiConverter {
+ override fun lift(value: Int): UInt {
+ return value.toUInt()
+ }
+
+ override fun read(buf: ByteBuffer): UInt {
+ return lift(buf.getInt())
+ }
+
+ override fun lower(value: UInt): Int {
+ return value.toInt()
+ }
+
+ override fun allocationSize(value: UInt) = 4
+
+ override fun write(value: UInt, buf: ByteBuffer) {
+ buf.putInt(value.toInt())
+ }
+}
+
+public object FfiConverterULong: FfiConverter {
+ override fun lift(value: Long): ULong {
+ return value.toULong()
+ }
+
+ override fun read(buf: ByteBuffer): ULong {
+ return lift(buf.getLong())
+ }
+
+ override fun lower(value: ULong): Long {
+ return value.toLong()
+ }
+
+ override fun allocationSize(value: ULong) = 8
+
+ override fun write(value: ULong, buf: ByteBuffer) {
+ buf.putLong(value.toLong())
+ }
+}
+
+public object FfiConverterBoolean: FfiConverter {
+ override fun lift(value: Byte): Boolean {
+ return value.toInt() != 0
+ }
+
+ override fun read(buf: ByteBuffer): Boolean {
+ return lift(buf.get())
+ }
+
+ override fun lower(value: Boolean): Byte {
+ return if (value) 1.toByte() else 0.toByte()
+ }
+
+ override fun allocationSize(value: Boolean) = 1
+
+ override fun write(value: Boolean, buf: ByteBuffer) {
+ buf.put(lower(value))
+ }
+}
+
+public object FfiConverterString: FfiConverter {
+ // Note: we don't inherit from FfiConverterRustBuffer, because we use a
+ // special encoding when lowering/lifting. We can use `RustBuffer.len` to
+ // store our length and avoid writing it out to the buffer.
+ override fun lift(value: RustBuffer.ByValue): String {
+ try {
+ val byteArr = ByteArray(value.len)
+ value.asByteBuffer()!!.get(byteArr)
+ return byteArr.toString(Charsets.UTF_8)
+ } finally {
+ RustBuffer.free(value)
+ }
+ }
+
+ override fun read(buf: ByteBuffer): String {
+ val len = buf.getInt()
+ val byteArr = ByteArray(len)
+ buf.get(byteArr)
+ return byteArr.toString(Charsets.UTF_8)
+ }
+
+ override fun lower(value: String): RustBuffer.ByValue {
+ val byteArr = value.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
+ }
+
+ // We aren't sure exactly how many bytes our string will be once it's UTF-8
+ // encoded. Allocate 3 bytes per unicode codepoint which will always be
+ // enough.
+ override fun allocationSize(value: String): Int {
+ val sizeForLength = 4
+ val sizeForString = value.length * 3
+ return sizeForLength + sizeForString
+ }
+
+ override fun write(value: String, buf: ByteBuffer) {
+ val byteArr = value.toByteArray(Charsets.UTF_8)
+ buf.putInt(byteArr.size)
+ buf.put(byteArr)
+ }
+}
+
+
+// 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()
+ companion object {
+ fun destroy(vararg args: Any?) {
+ args.filterIsInstance()
+ .forEach(Disposable::destroy)
+ }
+ }
+}
+
+inline fun 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` 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` 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 {
+
+ private val wasDestroyed = AtomicBoolean(false)
+ private 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 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()
+ }
+ }
+ }
+}
+
+public interface BuilderInterface {
+
+ fun `build`(): Node
+
+}
+
+class Builder(
+ pointer: Pointer
+) : FFIObject(pointer), BuilderInterface {
+ constructor() :
+ this(
+ rustCall() { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Builder_new( _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_ldk_node_ad4b_Builder_object_free(this.pointer, status)
+ }
+ }
+
+ override fun `build`(): Node =
+ callWithPointer {
+ rustCall() { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Builder_build(it, _status)
+}
+ }.let {
+ FfiConverterTypeNode.lift(it)
+ }
+
+
+
+}
+
+public object FfiConverterTypeBuilder: FfiConverter {
+ override fun lower(value: Builder): Pointer = value.callWithPointer { it }
+
+ override fun lift(value: Pointer): Builder {
+ return Builder(value)
+ }
+
+ override fun read(buf: ByteBuffer): Builder {
+ // The Rust code always writes pointers as 8 bytes, and will
+ // fail to compile if they don't fit.
+ return lift(Pointer(buf.getLong()))
+ }
+
+ override fun allocationSize(value: Builder) = 8
+
+ override fun write(value: Builder, buf: ByteBuffer) {
+ // The Rust code always expects pointers written as 8 bytes,
+ // and will fail to compile if they don't fit.
+ buf.putLong(Pointer.nativeValue(lower(value)))
+ }
+}
+
+
+
+
+public interface NodeInterface {
+
+ @Throws(NodeException::class)
+ fun `start`()
+
+ @Throws(NodeException::class)
+ fun `stop`()
+
+ fun `nextEvent`(): Event
+
+ fun `eventHandled`()
+
+ @Throws(NodeException::class)
+ fun `nodeId`(): PublicKey
+
+ @Throws(NodeException::class)
+ fun `newFundingAddress`(): Address
+
+ @Throws(NodeException::class)
+ fun `connectOpenChannel`(`nodePubkeyAndAddress`: String, `channelAmountSats`: ULong, `announceChannel`: Boolean)
+
+ @Throws(NodeException::class)
+ fun `sendPayment`(`invoice`: Invoice): PaymentHash
+
+ @Throws(NodeException::class)
+ fun `sendSpontaneousPayment`(`amountMsat`: ULong, `nodeId`: String): PaymentHash
+
+ @Throws(NodeException::class)
+ fun `receivePayment`(`amountMsat`: ULong?, `description`: String, `expirySecs`: UInt): Invoice
+
+}
+
+class Node(
+ pointer: Pointer
+) : FFIObject(pointer), NodeInterface {
+
+ /**
+ * 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_ldk_node_ad4b_Node_object_free(this.pointer, status)
+ }
+ }
+
+
+ @Throws(NodeException::class)override fun `start`() =
+ callWithPointer {
+ rustCallWithError(NodeException) { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_start(it, _status)
+}
+ }
+
+
+ @Throws(NodeException::class)override fun `stop`() =
+ callWithPointer {
+ rustCallWithError(NodeException) { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_stop(it, _status)
+}
+ }
+
+ override fun `nextEvent`(): Event =
+ callWithPointer {
+ rustCall() { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_next_event(it, _status)
+}
+ }.let {
+ FfiConverterTypeEvent.lift(it)
+ }
+ override fun `eventHandled`() =
+ callWithPointer {
+ rustCall() { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_event_handled(it, _status)
+}
+ }
+
+
+ @Throws(NodeException::class)override fun `nodeId`(): PublicKey =
+ callWithPointer {
+ rustCallWithError(NodeException) { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_node_id(it, _status)
+}
+ }.let {
+ FfiConverterTypePublicKey.lift(it)
+ }
+
+ @Throws(NodeException::class)override fun `newFundingAddress`(): Address =
+ callWithPointer {
+ rustCallWithError(NodeException) { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_new_funding_address(it, _status)
+}
+ }.let {
+ FfiConverterTypeAddress.lift(it)
+ }
+
+ @Throws(NodeException::class)override fun `connectOpenChannel`(`nodePubkeyAndAddress`: String, `channelAmountSats`: ULong, `announceChannel`: Boolean) =
+ callWithPointer {
+ rustCallWithError(NodeException) { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_connect_open_channel(it, FfiConverterString.lower(`nodePubkeyAndAddress`), FfiConverterULong.lower(`channelAmountSats`), FfiConverterBoolean.lower(`announceChannel`), _status)
+}
+ }
+
+
+ @Throws(NodeException::class)override fun `sendPayment`(`invoice`: Invoice): PaymentHash =
+ callWithPointer {
+ rustCallWithError(NodeException) { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_send_payment(it, FfiConverterTypeInvoice.lower(`invoice`), _status)
+}
+ }.let {
+ FfiConverterTypePaymentHash.lift(it)
+ }
+
+ @Throws(NodeException::class)override fun `sendSpontaneousPayment`(`amountMsat`: ULong, `nodeId`: String): PaymentHash =
+ callWithPointer {
+ rustCallWithError(NodeException) { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_send_spontaneous_payment(it, FfiConverterULong.lower(`amountMsat`), FfiConverterString.lower(`nodeId`), _status)
+}
+ }.let {
+ FfiConverterTypePaymentHash.lift(it)
+ }
+
+ @Throws(NodeException::class)override fun `receivePayment`(`amountMsat`: ULong?, `description`: String, `expirySecs`: UInt): Invoice =
+ callWithPointer {
+ rustCallWithError(NodeException) { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_receive_payment(it, FfiConverterOptionalULong.lower(`amountMsat`), FfiConverterString.lower(`description`), FfiConverterUInt.lower(`expirySecs`), _status)
+}
+ }.let {
+ FfiConverterTypeInvoice.lift(it)
+ }
+
+
+
+}
+
+public object FfiConverterTypeNode: FfiConverter {
+ override fun lower(value: Node): Pointer = value.callWithPointer { it }
+
+ override fun lift(value: Pointer): Node {
+ return Node(value)
+ }
+
+ override fun read(buf: ByteBuffer): Node {
+ // The Rust code always writes pointers as 8 bytes, and will
+ // fail to compile if they don't fit.
+ return lift(Pointer(buf.getLong()))
+ }
+
+ override fun allocationSize(value: Node) = 8
+
+ override fun write(value: Node, buf: ByteBuffer) {
+ // The Rust code always expects pointers written as 8 bytes,
+ // and will fail to compile if they don't fit.
+ buf.putLong(Pointer.nativeValue(lower(value)))
+ }
+}
+
+
+
+
+sealed class Event {
+ data class PaymentSuccessful(
+ val `paymentHash`: PaymentHash
+ ) : Event()
+ data class PaymentFailed(
+ val `paymentHash`: PaymentHash
+ ) : Event()
+ data class PaymentReceived(
+ val `paymentHash`: PaymentHash,
+ val `amountMsat`: ULong
+ ) : Event()
+ data class ChannelReady(
+ val `channelId`: ChannelId,
+ val `userChannelId`: UserChannelId
+ ) : Event()
+ data class ChannelClosed(
+ val `channelId`: ChannelId,
+ val `userChannelId`: UserChannelId
+ ) : Event()
+
+
+
+}
+
+public object FfiConverterTypeEvent : FfiConverterRustBuffer{
+ override fun read(buf: ByteBuffer): Event {
+ return when(buf.getInt()) {
+ 1 -> Event.PaymentSuccessful(
+ FfiConverterTypePaymentHash.read(buf),
+ )
+ 2 -> Event.PaymentFailed(
+ FfiConverterTypePaymentHash.read(buf),
+ )
+ 3 -> Event.PaymentReceived(
+ FfiConverterTypePaymentHash.read(buf),
+ FfiConverterULong.read(buf),
+ )
+ 4 -> Event.ChannelReady(
+ FfiConverterTypeChannelId.read(buf),
+ FfiConverterTypeUserChannelId.read(buf),
+ )
+ 5 -> Event.ChannelClosed(
+ FfiConverterTypeChannelId.read(buf),
+ FfiConverterTypeUserChannelId.read(buf),
+ )
+ else -> throw RuntimeException("invalid enum value, something is very wrong!!")
+ }
+ }
+
+ override fun allocationSize(value: Event) = when(value) {
+ is Event.PaymentSuccessful -> {
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ (
+ 4
+ + FfiConverterTypePaymentHash.allocationSize(value.`paymentHash`)
+ )
+ }
+ is Event.PaymentFailed -> {
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ (
+ 4
+ + FfiConverterTypePaymentHash.allocationSize(value.`paymentHash`)
+ )
+ }
+ is Event.PaymentReceived -> {
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ (
+ 4
+ + FfiConverterTypePaymentHash.allocationSize(value.`paymentHash`)
+ + FfiConverterULong.allocationSize(value.`amountMsat`)
+ )
+ }
+ is Event.ChannelReady -> {
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ (
+ 4
+ + FfiConverterTypeChannelId.allocationSize(value.`channelId`)
+ + FfiConverterTypeUserChannelId.allocationSize(value.`userChannelId`)
+ )
+ }
+ is Event.ChannelClosed -> {
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ (
+ 4
+ + FfiConverterTypeChannelId.allocationSize(value.`channelId`)
+ + FfiConverterTypeUserChannelId.allocationSize(value.`userChannelId`)
+ )
+ }
+ }
+
+ override fun write(value: Event, buf: ByteBuffer) {
+ when(value) {
+ is Event.PaymentSuccessful -> {
+ buf.putInt(1)
+ FfiConverterTypePaymentHash.write(value.`paymentHash`, buf)
+ Unit
+ }
+ is Event.PaymentFailed -> {
+ buf.putInt(2)
+ FfiConverterTypePaymentHash.write(value.`paymentHash`, buf)
+ Unit
+ }
+ is Event.PaymentReceived -> {
+ buf.putInt(3)
+ FfiConverterTypePaymentHash.write(value.`paymentHash`, buf)
+ FfiConverterULong.write(value.`amountMsat`, buf)
+ Unit
+ }
+ is Event.ChannelReady -> {
+ buf.putInt(4)
+ FfiConverterTypeChannelId.write(value.`channelId`, buf)
+ FfiConverterTypeUserChannelId.write(value.`userChannelId`, buf)
+ Unit
+ }
+ is Event.ChannelClosed -> {
+ buf.putInt(5)
+ FfiConverterTypeChannelId.write(value.`channelId`, buf)
+ FfiConverterTypeUserChannelId.write(value.`userChannelId`, buf)
+ Unit
+ }
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+}
+
+
+
+
+
+
+
+sealed class NodeException(message: String): Exception(message) {
+ // Each variant is a nested class
+ // Flat enums carries a string error message, so no special implementation is necessary.
+ class AlreadyRunning(message: String) : NodeException(message)
+ class NotRunning(message: String) : NodeException(message)
+ class FundingTxCreationFailed(message: String) : NodeException(message)
+ class ConnectionFailed(message: String) : NodeException(message)
+ class AddressInvalid(message: String) : NodeException(message)
+ class PublicKeyInvalid(message: String) : NodeException(message)
+ class PaymentHashInvalid(message: String) : NodeException(message)
+ class NonUniquePaymentHash(message: String) : NodeException(message)
+ class InvoiceInvalid(message: String) : NodeException(message)
+ class InvoiceCreationFailed(message: String) : NodeException(message)
+ class ChannelIdInvalid(message: String) : NodeException(message)
+ class RoutingFailed(message: String) : NodeException(message)
+ class PeerInfoParseFailed(message: String) : NodeException(message)
+ class ChannelCreationFailed(message: String) : NodeException(message)
+ class ChannelClosingFailed(message: String) : NodeException(message)
+ class PersistenceFailed(message: String) : NodeException(message)
+ class WalletOperationFailed(message: String) : NodeException(message)
+ class WalletSigningFailed(message: String) : NodeException(message)
+ class TxSyncFailed(message: String) : NodeException(message)
+
+
+ companion object ErrorHandler : CallStatusErrorHandler {
+ override fun lift(error_buf: RustBuffer.ByValue): NodeException = FfiConverterTypeNodeError.lift(error_buf)
+ }
+}
+
+public object FfiConverterTypeNodeError : FfiConverterRustBuffer {
+ override fun read(buf: ByteBuffer): NodeException {
+
+ return when(buf.getInt()) {
+ 1 -> NodeException.AlreadyRunning(FfiConverterString.read(buf))
+ 2 -> NodeException.NotRunning(FfiConverterString.read(buf))
+ 3 -> NodeException.FundingTxCreationFailed(FfiConverterString.read(buf))
+ 4 -> NodeException.ConnectionFailed(FfiConverterString.read(buf))
+ 5 -> NodeException.AddressInvalid(FfiConverterString.read(buf))
+ 6 -> NodeException.PublicKeyInvalid(FfiConverterString.read(buf))
+ 7 -> NodeException.PaymentHashInvalid(FfiConverterString.read(buf))
+ 8 -> NodeException.NonUniquePaymentHash(FfiConverterString.read(buf))
+ 9 -> NodeException.InvoiceInvalid(FfiConverterString.read(buf))
+ 10 -> NodeException.InvoiceCreationFailed(FfiConverterString.read(buf))
+ 11 -> NodeException.ChannelIdInvalid(FfiConverterString.read(buf))
+ 12 -> NodeException.RoutingFailed(FfiConverterString.read(buf))
+ 13 -> NodeException.PeerInfoParseFailed(FfiConverterString.read(buf))
+ 14 -> NodeException.ChannelCreationFailed(FfiConverterString.read(buf))
+ 15 -> NodeException.ChannelClosingFailed(FfiConverterString.read(buf))
+ 16 -> NodeException.PersistenceFailed(FfiConverterString.read(buf))
+ 17 -> NodeException.WalletOperationFailed(FfiConverterString.read(buf))
+ 18 -> NodeException.WalletSigningFailed(FfiConverterString.read(buf))
+ 19 -> NodeException.TxSyncFailed(FfiConverterString.read(buf))
+ else -> throw RuntimeException("invalid error enum value, something is very wrong!!")
+ }
+
+ }
+
+ override fun allocationSize(value: NodeException): Int {
+ return 4
+ }
+
+ override fun write(value: NodeException, buf: ByteBuffer) {
+ when(value) {
+ is NodeException.AlreadyRunning -> {
+ buf.putInt(1)
+ Unit
+ }
+ is NodeException.NotRunning -> {
+ buf.putInt(2)
+ Unit
+ }
+ is NodeException.FundingTxCreationFailed -> {
+ buf.putInt(3)
+ Unit
+ }
+ is NodeException.ConnectionFailed -> {
+ buf.putInt(4)
+ Unit
+ }
+ is NodeException.AddressInvalid -> {
+ buf.putInt(5)
+ Unit
+ }
+ is NodeException.PublicKeyInvalid -> {
+ buf.putInt(6)
+ Unit
+ }
+ is NodeException.PaymentHashInvalid -> {
+ buf.putInt(7)
+ Unit
+ }
+ is NodeException.NonUniquePaymentHash -> {
+ buf.putInt(8)
+ Unit
+ }
+ is NodeException.InvoiceInvalid -> {
+ buf.putInt(9)
+ Unit
+ }
+ is NodeException.InvoiceCreationFailed -> {
+ buf.putInt(10)
+ Unit
+ }
+ is NodeException.ChannelIdInvalid -> {
+ buf.putInt(11)
+ Unit
+ }
+ is NodeException.RoutingFailed -> {
+ buf.putInt(12)
+ Unit
+ }
+ is NodeException.PeerInfoParseFailed -> {
+ buf.putInt(13)
+ Unit
+ }
+ is NodeException.ChannelCreationFailed -> {
+ buf.putInt(14)
+ Unit
+ }
+ is NodeException.ChannelClosingFailed -> {
+ buf.putInt(15)
+ Unit
+ }
+ is NodeException.PersistenceFailed -> {
+ buf.putInt(16)
+ Unit
+ }
+ is NodeException.WalletOperationFailed -> {
+ buf.putInt(17)
+ Unit
+ }
+ is NodeException.WalletSigningFailed -> {
+ buf.putInt(18)
+ Unit
+ }
+ is NodeException.TxSyncFailed -> {
+ buf.putInt(19)
+ Unit
+ }
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+
+}
+
+
+
+
+public object FfiConverterOptionalULong: FfiConverterRustBuffer {
+ override fun read(buf: ByteBuffer): ULong? {
+ if (buf.get().toInt() == 0) {
+ return null
+ }
+ return FfiConverterULong.read(buf)
+ }
+
+ override fun allocationSize(value: ULong?): Int {
+ if (value == null) {
+ return 1
+ } else {
+ return 1 + FfiConverterULong.allocationSize(value)
+ }
+ }
+
+ override fun write(value: ULong?, buf: ByteBuffer) {
+ if (value == null) {
+ buf.put(0)
+ } else {
+ buf.put(1)
+ FfiConverterULong.write(value, buf)
+ }
+ }
+}
+
+
+
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias Address = String
+public typealias FfiConverterTypeAddress = FfiConverterString
+
+
+
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias ChannelId = String
+public typealias FfiConverterTypeChannelId = FfiConverterString
+
+
+
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias Invoice = String
+public typealias FfiConverterTypeInvoice = FfiConverterString
+
+
+
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias PaymentHash = String
+public typealias FfiConverterTypePaymentHash = FfiConverterString
+
+
+
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias PublicKey = String
+public typealias FfiConverterTypePublicKey = FfiConverterString
+
+
+
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias UserChannelId = String
+public typealias FfiConverterTypeUserChannelId = FfiConverterString
+
+
diff --git a/ldk-node-jvm/lib/src/test/kotlin/ldk/node/LibraryTest.kt b/ldk-node-jvm/lib/src/test/kotlin/ldk/node/LibraryTest.kt
new file mode 100644
index 000000000..3914cb939
--- /dev/null
+++ b/ldk-node-jvm/lib/src/test/kotlin/ldk/node/LibraryTest.kt
@@ -0,0 +1,14 @@
+/*
+ * This Kotlin source file was generated by the Gradle 'init' task.
+ */
+package ldk.node
+
+import kotlin.test.Test
+import kotlin.test.assertTrue
+
+class LibraryTest {
+ @Test fun someLibraryMethodReturnsTrue() {
+ val classUnderTest = Library()
+ assertTrue(classUnderTest.someLibraryMethod(), "someLibraryMethod should return 'true'")
+ }
+}
diff --git a/ldk-node-jvm/settings.gradle.kts b/ldk-node-jvm/settings.gradle.kts
new file mode 100644
index 000000000..75e57139a
--- /dev/null
+++ b/ldk-node-jvm/settings.gradle.kts
@@ -0,0 +1,2 @@
+rootProject.name = "ldk-node-jvm"
+include("lib")
diff --git a/uniffi/uniffi/ldk_node/ldk_node.kt b/uniffi/uniffi/ldk_node/ldk_node.kt
new file mode 100644
index 000000000..56452d287
--- /dev/null
+++ b/uniffi/uniffi/ldk_node/ldk_node.kt
@@ -0,0 +1,1203 @@
+// 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.ldk_node;
+
+// 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 com.sun.jna.ptr.ByReference
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicLong
+
+// 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_ldk_node_ad4b_rustbuffer_alloc(size, status).also {
+ if(it.data == null) {
+ throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})")
+ }
+ }
+ }
+
+ internal fun free(buf: RustBuffer.ByValue) = rustCall() { status ->
+ _UniFFILib.INSTANCE.ffi_ldk_node_ad4b_rustbuffer_free(buf, status)
+ }
+ }
+
+ @Suppress("TooGenericExceptionThrown")
+ fun asByteBuffer() =
+ this.data?.getByteBuffer(0, this.len.toLong())?.also {
+ it.order(ByteOrder.BIG_ENDIAN)
+ }
+}
+
+/**
+ * The equivalent of the `*mut RustBuffer` type.
+ * Required for callbacks taking in an out pointer.
+ *
+ * Size is the sum of all values in the struct.
+ */
+class RustBufferByReference : ByReference(16) {
+ /**
+ * Set the pointed-to `RustBuffer` to the given value.
+ */
+ fun setValue(value: RustBuffer.ByValue) {
+ // NOTE: The offsets are as they are in the C-like struct.
+ val pointer = getPointer()
+ pointer.setInt(0, value.capacity)
+ pointer.setInt(4, value.len)
+ pointer.setPointer(8, value.data)
+ }
+}
+
+// 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
+}
+// The FfiConverter interface handles converter types to and from the FFI
+//
+// All implementing objects should be public to support external types. When a
+// type is external we need to import it's FfiConverter.
+public interface FfiConverter {
+ // Convert an FFI type to a Kotlin type
+ fun lift(value: FfiType): KotlinType
+
+ // Convert an Kotlin type to an FFI type
+ fun lower(value: KotlinType): FfiType
+
+ // Read a Kotlin type from a `ByteBuffer`
+ fun read(buf: ByteBuffer): KotlinType
+
+ // Calculate bytes to allocate when creating a `RustBuffer`
+ //
+ // This must return at least as many bytes as the write() function will
+ // write. It can return more bytes than needed, for example when writing
+ // Strings we can't know the exact bytes needed until we the UTF-8
+ // encoding, so we pessimistically allocate the largest size possible (3
+ // bytes per codepoint). Allocating extra bytes is not really a big deal
+ // because the `RustBuffer` is short-lived.
+ fun allocationSize(value: KotlinType): Int
+
+ // Write a Kotlin type to a `ByteBuffer`
+ fun write(value: KotlinType, buf: ByteBuffer)
+
+ // Lower a value into a `RustBuffer`
+ //
+ // This method lowers a value into a `RustBuffer` rather than the normal
+ // FfiType. It's used by the callback interface code. Callback interface
+ // returns are always serialized into a `RustBuffer` regardless of their
+ // normal FFI type.
+ fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue {
+ val rbuf = RustBuffer.alloc(allocationSize(value))
+ try {
+ val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity.toLong()).also {
+ it.order(ByteOrder.BIG_ENDIAN)
+ }
+ write(value, bbuf)
+ rbuf.writeField("len", bbuf.position())
+ return rbuf
+ } catch (e: Throwable) {
+ RustBuffer.free(rbuf)
+ throw e
+ }
+ }
+
+ // Lift a value from a `RustBuffer`.
+ //
+ // This here mostly because of the symmetry with `lowerIntoRustBuffer()`.
+ // It's currently only used by the `FfiConverterRustBuffer` class below.
+ fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType {
+ val byteBuf = rbuf.asByteBuffer()!!
+ try {
+ val item = read(byteBuf)
+ if (byteBuf.hasRemaining()) {
+ throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!")
+ }
+ return item
+ } finally {
+ RustBuffer.free(rbuf)
+ }
+ }
+}
+
+// FfiConverter that uses `RustBuffer` as the FfiType
+public interface FfiConverterRustBuffer: FfiConverter {
+ override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value)
+ override fun lower(value: KotlinType) = lowerIntoRustBuffer(value)
+}
+// 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.
+// Error runtime.
+@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 {
+ fun lift(error_buf: RustBuffer.ByValue): E;
+}
+
+// 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 rustCallWithError(errorHandler: CallStatusErrorHandler, 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(FfiConverterString.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 {
+ 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 rustCall(callback: (RustCallStatus) -> U): U {
+ return rustCallWithError(NullCallStatusErrorHandler, callback);
+}
+
+// Contains loading, initialization code,
+// and the FFI Function declarations in a com.sun.jna.Library.
+@Synchronized
+private fun findLibraryName(componentName: String): String {
+ val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride")
+ if (libOverride != null) {
+ return libOverride
+ }
+ return "uniffi_ldk_node"
+}
+
+private inline fun loadIndirect(
+ componentName: String
+): Lib {
+ return Native.load(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 = "ldk_node")
+
+ }
+ }
+
+ fun ffi_ldk_node_ad4b_Builder_object_free(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): Unit
+
+ fun ldk_node_ad4b_Builder_new(
+ _uniffi_out_err: RustCallStatus
+ ): Pointer
+
+ fun ldk_node_ad4b_Builder_build(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): Pointer
+
+ fun ffi_ldk_node_ad4b_Node_object_free(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): Unit
+
+ fun ldk_node_ad4b_Node_start(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): Unit
+
+ fun ldk_node_ad4b_Node_stop(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): Unit
+
+ fun ldk_node_ad4b_Node_next_event(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+ fun ldk_node_ad4b_Node_event_handled(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): Unit
+
+ fun ldk_node_ad4b_Node_node_id(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+ fun ldk_node_ad4b_Node_new_funding_address(`ptr`: Pointer,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+ fun ldk_node_ad4b_Node_connect_open_channel(`ptr`: Pointer,`nodePubkeyAndAddress`: RustBuffer.ByValue,`channelAmountSats`: Long,`announceChannel`: Byte,
+ _uniffi_out_err: RustCallStatus
+ ): Unit
+
+ fun ldk_node_ad4b_Node_send_payment(`ptr`: Pointer,`invoice`: RustBuffer.ByValue,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+ fun ldk_node_ad4b_Node_send_spontaneous_payment(`ptr`: Pointer,`amountMsat`: Long,`nodeId`: RustBuffer.ByValue,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+ fun ldk_node_ad4b_Node_receive_payment(`ptr`: Pointer,`amountMsat`: RustBuffer.ByValue,`description`: RustBuffer.ByValue,`expirySecs`: Int,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+ fun ffi_ldk_node_ad4b_rustbuffer_alloc(`size`: Int,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+ fun ffi_ldk_node_ad4b_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+ fun ffi_ldk_node_ad4b_rustbuffer_free(`buf`: RustBuffer.ByValue,
+ _uniffi_out_err: RustCallStatus
+ ): Unit
+
+ fun ffi_ldk_node_ad4b_rustbuffer_reserve(`buf`: RustBuffer.ByValue,`additional`: Int,
+ _uniffi_out_err: RustCallStatus
+ ): RustBuffer.ByValue
+
+
+}
+
+// Public interface members begin here.
+
+
+public object FfiConverterUInt: FfiConverter {
+ override fun lift(value: Int): UInt {
+ return value.toUInt()
+ }
+
+ override fun read(buf: ByteBuffer): UInt {
+ return lift(buf.getInt())
+ }
+
+ override fun lower(value: UInt): Int {
+ return value.toInt()
+ }
+
+ override fun allocationSize(value: UInt) = 4
+
+ override fun write(value: UInt, buf: ByteBuffer) {
+ buf.putInt(value.toInt())
+ }
+}
+
+public object FfiConverterULong: FfiConverter {
+ override fun lift(value: Long): ULong {
+ return value.toULong()
+ }
+
+ override fun read(buf: ByteBuffer): ULong {
+ return lift(buf.getLong())
+ }
+
+ override fun lower(value: ULong): Long {
+ return value.toLong()
+ }
+
+ override fun allocationSize(value: ULong) = 8
+
+ override fun write(value: ULong, buf: ByteBuffer) {
+ buf.putLong(value.toLong())
+ }
+}
+
+public object FfiConverterBoolean: FfiConverter {
+ override fun lift(value: Byte): Boolean {
+ return value.toInt() != 0
+ }
+
+ override fun read(buf: ByteBuffer): Boolean {
+ return lift(buf.get())
+ }
+
+ override fun lower(value: Boolean): Byte {
+ return if (value) 1.toByte() else 0.toByte()
+ }
+
+ override fun allocationSize(value: Boolean) = 1
+
+ override fun write(value: Boolean, buf: ByteBuffer) {
+ buf.put(lower(value))
+ }
+}
+
+public object FfiConverterString: FfiConverter {
+ // Note: we don't inherit from FfiConverterRustBuffer, because we use a
+ // special encoding when lowering/lifting. We can use `RustBuffer.len` to
+ // store our length and avoid writing it out to the buffer.
+ override fun lift(value: RustBuffer.ByValue): String {
+ try {
+ val byteArr = ByteArray(value.len)
+ value.asByteBuffer()!!.get(byteArr)
+ return byteArr.toString(Charsets.UTF_8)
+ } finally {
+ RustBuffer.free(value)
+ }
+ }
+
+ override fun read(buf: ByteBuffer): String {
+ val len = buf.getInt()
+ val byteArr = ByteArray(len)
+ buf.get(byteArr)
+ return byteArr.toString(Charsets.UTF_8)
+ }
+
+ override fun lower(value: String): RustBuffer.ByValue {
+ val byteArr = value.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
+ }
+
+ // We aren't sure exactly how many bytes our string will be once it's UTF-8
+ // encoded. Allocate 3 bytes per unicode codepoint which will always be
+ // enough.
+ override fun allocationSize(value: String): Int {
+ val sizeForLength = 4
+ val sizeForString = value.length * 3
+ return sizeForLength + sizeForString
+ }
+
+ override fun write(value: String, buf: ByteBuffer) {
+ val byteArr = value.toByteArray(Charsets.UTF_8)
+ buf.putInt(byteArr.size)
+ buf.put(byteArr)
+ }
+}
+
+
+// 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()
+ companion object {
+ fun destroy(vararg args: Any?) {
+ args.filterIsInstance()
+ .forEach(Disposable::destroy)
+ }
+ }
+}
+
+inline fun 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` 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` 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 {
+
+ private val wasDestroyed = AtomicBoolean(false)
+ private 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 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()
+ }
+ }
+ }
+}
+
+public interface BuilderInterface {
+
+ fun `build`(): Node
+
+}
+
+class Builder(
+ pointer: Pointer
+) : FFIObject(pointer), BuilderInterface {
+ constructor() :
+ this(
+ rustCall() { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Builder_new( _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_ldk_node_ad4b_Builder_object_free(this.pointer, status)
+ }
+ }
+
+ override fun `build`(): Node =
+ callWithPointer {
+ rustCall() { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Builder_build(it, _status)
+}
+ }.let {
+ FfiConverterTypeNode.lift(it)
+ }
+
+
+
+}
+
+public object FfiConverterTypeBuilder: FfiConverter {
+ override fun lower(value: Builder): Pointer = value.callWithPointer { it }
+
+ override fun lift(value: Pointer): Builder {
+ return Builder(value)
+ }
+
+ override fun read(buf: ByteBuffer): Builder {
+ // The Rust code always writes pointers as 8 bytes, and will
+ // fail to compile if they don't fit.
+ return lift(Pointer(buf.getLong()))
+ }
+
+ override fun allocationSize(value: Builder) = 8
+
+ override fun write(value: Builder, buf: ByteBuffer) {
+ // The Rust code always expects pointers written as 8 bytes,
+ // and will fail to compile if they don't fit.
+ buf.putLong(Pointer.nativeValue(lower(value)))
+ }
+}
+
+
+
+
+public interface NodeInterface {
+
+ @Throws(NodeException::class)
+ fun `start`()
+
+ @Throws(NodeException::class)
+ fun `stop`()
+
+ fun `nextEvent`(): Event
+
+ fun `eventHandled`()
+
+ @Throws(NodeException::class)
+ fun `nodeId`(): PublicKey
+
+ @Throws(NodeException::class)
+ fun `newFundingAddress`(): Address
+
+ @Throws(NodeException::class)
+ fun `connectOpenChannel`(`nodePubkeyAndAddress`: String, `channelAmountSats`: ULong, `announceChannel`: Boolean)
+
+ @Throws(NodeException::class)
+ fun `sendPayment`(`invoice`: Invoice): PaymentHash
+
+ @Throws(NodeException::class)
+ fun `sendSpontaneousPayment`(`amountMsat`: ULong, `nodeId`: String): PaymentHash
+
+ @Throws(NodeException::class)
+ fun `receivePayment`(`amountMsat`: ULong?, `description`: String, `expirySecs`: UInt): Invoice
+
+}
+
+class Node(
+ pointer: Pointer
+) : FFIObject(pointer), NodeInterface {
+
+ /**
+ * 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_ldk_node_ad4b_Node_object_free(this.pointer, status)
+ }
+ }
+
+
+ @Throws(NodeException::class)override fun `start`() =
+ callWithPointer {
+ rustCallWithError(NodeException) { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_start(it, _status)
+}
+ }
+
+
+ @Throws(NodeException::class)override fun `stop`() =
+ callWithPointer {
+ rustCallWithError(NodeException) { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_stop(it, _status)
+}
+ }
+
+ override fun `nextEvent`(): Event =
+ callWithPointer {
+ rustCall() { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_next_event(it, _status)
+}
+ }.let {
+ FfiConverterTypeEvent.lift(it)
+ }
+ override fun `eventHandled`() =
+ callWithPointer {
+ rustCall() { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_event_handled(it, _status)
+}
+ }
+
+
+ @Throws(NodeException::class)override fun `nodeId`(): PublicKey =
+ callWithPointer {
+ rustCallWithError(NodeException) { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_node_id(it, _status)
+}
+ }.let {
+ FfiConverterTypePublicKey.lift(it)
+ }
+
+ @Throws(NodeException::class)override fun `newFundingAddress`(): Address =
+ callWithPointer {
+ rustCallWithError(NodeException) { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_new_funding_address(it, _status)
+}
+ }.let {
+ FfiConverterTypeAddress.lift(it)
+ }
+
+ @Throws(NodeException::class)override fun `connectOpenChannel`(`nodePubkeyAndAddress`: String, `channelAmountSats`: ULong, `announceChannel`: Boolean) =
+ callWithPointer {
+ rustCallWithError(NodeException) { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_connect_open_channel(it, FfiConverterString.lower(`nodePubkeyAndAddress`), FfiConverterULong.lower(`channelAmountSats`), FfiConverterBoolean.lower(`announceChannel`), _status)
+}
+ }
+
+
+ @Throws(NodeException::class)override fun `sendPayment`(`invoice`: Invoice): PaymentHash =
+ callWithPointer {
+ rustCallWithError(NodeException) { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_send_payment(it, FfiConverterTypeInvoice.lower(`invoice`), _status)
+}
+ }.let {
+ FfiConverterTypePaymentHash.lift(it)
+ }
+
+ @Throws(NodeException::class)override fun `sendSpontaneousPayment`(`amountMsat`: ULong, `nodeId`: String): PaymentHash =
+ callWithPointer {
+ rustCallWithError(NodeException) { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_send_spontaneous_payment(it, FfiConverterULong.lower(`amountMsat`), FfiConverterString.lower(`nodeId`), _status)
+}
+ }.let {
+ FfiConverterTypePaymentHash.lift(it)
+ }
+
+ @Throws(NodeException::class)override fun `receivePayment`(`amountMsat`: ULong?, `description`: String, `expirySecs`: UInt): Invoice =
+ callWithPointer {
+ rustCallWithError(NodeException) { _status ->
+ _UniFFILib.INSTANCE.ldk_node_ad4b_Node_receive_payment(it, FfiConverterOptionalULong.lower(`amountMsat`), FfiConverterString.lower(`description`), FfiConverterUInt.lower(`expirySecs`), _status)
+}
+ }.let {
+ FfiConverterTypeInvoice.lift(it)
+ }
+
+
+
+}
+
+public object FfiConverterTypeNode: FfiConverter {
+ override fun lower(value: Node): Pointer = value.callWithPointer { it }
+
+ override fun lift(value: Pointer): Node {
+ return Node(value)
+ }
+
+ override fun read(buf: ByteBuffer): Node {
+ // The Rust code always writes pointers as 8 bytes, and will
+ // fail to compile if they don't fit.
+ return lift(Pointer(buf.getLong()))
+ }
+
+ override fun allocationSize(value: Node) = 8
+
+ override fun write(value: Node, buf: ByteBuffer) {
+ // The Rust code always expects pointers written as 8 bytes,
+ // and will fail to compile if they don't fit.
+ buf.putLong(Pointer.nativeValue(lower(value)))
+ }
+}
+
+
+
+
+sealed class Event {
+ data class PaymentSuccessful(
+ val `paymentHash`: PaymentHash
+ ) : Event()
+ data class PaymentFailed(
+ val `paymentHash`: PaymentHash
+ ) : Event()
+ data class PaymentReceived(
+ val `paymentHash`: PaymentHash,
+ val `amountMsat`: ULong
+ ) : Event()
+ data class ChannelReady(
+ val `channelId`: ChannelId,
+ val `userChannelId`: UserChannelId
+ ) : Event()
+ data class ChannelClosed(
+ val `channelId`: ChannelId,
+ val `userChannelId`: UserChannelId
+ ) : Event()
+
+
+
+}
+
+public object FfiConverterTypeEvent : FfiConverterRustBuffer{
+ override fun read(buf: ByteBuffer): Event {
+ return when(buf.getInt()) {
+ 1 -> Event.PaymentSuccessful(
+ FfiConverterTypePaymentHash.read(buf),
+ )
+ 2 -> Event.PaymentFailed(
+ FfiConverterTypePaymentHash.read(buf),
+ )
+ 3 -> Event.PaymentReceived(
+ FfiConverterTypePaymentHash.read(buf),
+ FfiConverterULong.read(buf),
+ )
+ 4 -> Event.ChannelReady(
+ FfiConverterTypeChannelId.read(buf),
+ FfiConverterTypeUserChannelId.read(buf),
+ )
+ 5 -> Event.ChannelClosed(
+ FfiConverterTypeChannelId.read(buf),
+ FfiConverterTypeUserChannelId.read(buf),
+ )
+ else -> throw RuntimeException("invalid enum value, something is very wrong!!")
+ }
+ }
+
+ override fun allocationSize(value: Event) = when(value) {
+ is Event.PaymentSuccessful -> {
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ (
+ 4
+ + FfiConverterTypePaymentHash.allocationSize(value.`paymentHash`)
+ )
+ }
+ is Event.PaymentFailed -> {
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ (
+ 4
+ + FfiConverterTypePaymentHash.allocationSize(value.`paymentHash`)
+ )
+ }
+ is Event.PaymentReceived -> {
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ (
+ 4
+ + FfiConverterTypePaymentHash.allocationSize(value.`paymentHash`)
+ + FfiConverterULong.allocationSize(value.`amountMsat`)
+ )
+ }
+ is Event.ChannelReady -> {
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ (
+ 4
+ + FfiConverterTypeChannelId.allocationSize(value.`channelId`)
+ + FfiConverterTypeUserChannelId.allocationSize(value.`userChannelId`)
+ )
+ }
+ is Event.ChannelClosed -> {
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ (
+ 4
+ + FfiConverterTypeChannelId.allocationSize(value.`channelId`)
+ + FfiConverterTypeUserChannelId.allocationSize(value.`userChannelId`)
+ )
+ }
+ }
+
+ override fun write(value: Event, buf: ByteBuffer) {
+ when(value) {
+ is Event.PaymentSuccessful -> {
+ buf.putInt(1)
+ FfiConverterTypePaymentHash.write(value.`paymentHash`, buf)
+ Unit
+ }
+ is Event.PaymentFailed -> {
+ buf.putInt(2)
+ FfiConverterTypePaymentHash.write(value.`paymentHash`, buf)
+ Unit
+ }
+ is Event.PaymentReceived -> {
+ buf.putInt(3)
+ FfiConverterTypePaymentHash.write(value.`paymentHash`, buf)
+ FfiConverterULong.write(value.`amountMsat`, buf)
+ Unit
+ }
+ is Event.ChannelReady -> {
+ buf.putInt(4)
+ FfiConverterTypeChannelId.write(value.`channelId`, buf)
+ FfiConverterTypeUserChannelId.write(value.`userChannelId`, buf)
+ Unit
+ }
+ is Event.ChannelClosed -> {
+ buf.putInt(5)
+ FfiConverterTypeChannelId.write(value.`channelId`, buf)
+ FfiConverterTypeUserChannelId.write(value.`userChannelId`, buf)
+ Unit
+ }
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+}
+
+
+
+
+
+
+
+sealed class NodeException(message: String): Exception(message) {
+ // Each variant is a nested class
+ // Flat enums carries a string error message, so no special implementation is necessary.
+ class AlreadyRunning(message: String) : NodeException(message)
+ class NotRunning(message: String) : NodeException(message)
+ class FundingTxCreationFailed(message: String) : NodeException(message)
+ class ConnectionFailed(message: String) : NodeException(message)
+ class AddressInvalid(message: String) : NodeException(message)
+ class PublicKeyInvalid(message: String) : NodeException(message)
+ class PaymentHashInvalid(message: String) : NodeException(message)
+ class NonUniquePaymentHash(message: String) : NodeException(message)
+ class InvoiceInvalid(message: String) : NodeException(message)
+ class InvoiceCreationFailed(message: String) : NodeException(message)
+ class ChannelIdInvalid(message: String) : NodeException(message)
+ class RoutingFailed(message: String) : NodeException(message)
+ class PeerInfoParseFailed(message: String) : NodeException(message)
+ class ChannelCreationFailed(message: String) : NodeException(message)
+ class ChannelClosingFailed(message: String) : NodeException(message)
+ class PersistenceFailed(message: String) : NodeException(message)
+ class WalletOperationFailed(message: String) : NodeException(message)
+ class WalletSigningFailed(message: String) : NodeException(message)
+ class TxSyncFailed(message: String) : NodeException(message)
+
+
+ companion object ErrorHandler : CallStatusErrorHandler {
+ override fun lift(error_buf: RustBuffer.ByValue): NodeException = FfiConverterTypeNodeError.lift(error_buf)
+ }
+}
+
+public object FfiConverterTypeNodeError : FfiConverterRustBuffer {
+ override fun read(buf: ByteBuffer): NodeException {
+
+ return when(buf.getInt()) {
+ 1 -> NodeException.AlreadyRunning(FfiConverterString.read(buf))
+ 2 -> NodeException.NotRunning(FfiConverterString.read(buf))
+ 3 -> NodeException.FundingTxCreationFailed(FfiConverterString.read(buf))
+ 4 -> NodeException.ConnectionFailed(FfiConverterString.read(buf))
+ 5 -> NodeException.AddressInvalid(FfiConverterString.read(buf))
+ 6 -> NodeException.PublicKeyInvalid(FfiConverterString.read(buf))
+ 7 -> NodeException.PaymentHashInvalid(FfiConverterString.read(buf))
+ 8 -> NodeException.NonUniquePaymentHash(FfiConverterString.read(buf))
+ 9 -> NodeException.InvoiceInvalid(FfiConverterString.read(buf))
+ 10 -> NodeException.InvoiceCreationFailed(FfiConverterString.read(buf))
+ 11 -> NodeException.ChannelIdInvalid(FfiConverterString.read(buf))
+ 12 -> NodeException.RoutingFailed(FfiConverterString.read(buf))
+ 13 -> NodeException.PeerInfoParseFailed(FfiConverterString.read(buf))
+ 14 -> NodeException.ChannelCreationFailed(FfiConverterString.read(buf))
+ 15 -> NodeException.ChannelClosingFailed(FfiConverterString.read(buf))
+ 16 -> NodeException.PersistenceFailed(FfiConverterString.read(buf))
+ 17 -> NodeException.WalletOperationFailed(FfiConverterString.read(buf))
+ 18 -> NodeException.WalletSigningFailed(FfiConverterString.read(buf))
+ 19 -> NodeException.TxSyncFailed(FfiConverterString.read(buf))
+ else -> throw RuntimeException("invalid error enum value, something is very wrong!!")
+ }
+
+ }
+
+ override fun allocationSize(value: NodeException): Int {
+ return 4
+ }
+
+ override fun write(value: NodeException, buf: ByteBuffer) {
+ when(value) {
+ is NodeException.AlreadyRunning -> {
+ buf.putInt(1)
+ Unit
+ }
+ is NodeException.NotRunning -> {
+ buf.putInt(2)
+ Unit
+ }
+ is NodeException.FundingTxCreationFailed -> {
+ buf.putInt(3)
+ Unit
+ }
+ is NodeException.ConnectionFailed -> {
+ buf.putInt(4)
+ Unit
+ }
+ is NodeException.AddressInvalid -> {
+ buf.putInt(5)
+ Unit
+ }
+ is NodeException.PublicKeyInvalid -> {
+ buf.putInt(6)
+ Unit
+ }
+ is NodeException.PaymentHashInvalid -> {
+ buf.putInt(7)
+ Unit
+ }
+ is NodeException.NonUniquePaymentHash -> {
+ buf.putInt(8)
+ Unit
+ }
+ is NodeException.InvoiceInvalid -> {
+ buf.putInt(9)
+ Unit
+ }
+ is NodeException.InvoiceCreationFailed -> {
+ buf.putInt(10)
+ Unit
+ }
+ is NodeException.ChannelIdInvalid -> {
+ buf.putInt(11)
+ Unit
+ }
+ is NodeException.RoutingFailed -> {
+ buf.putInt(12)
+ Unit
+ }
+ is NodeException.PeerInfoParseFailed -> {
+ buf.putInt(13)
+ Unit
+ }
+ is NodeException.ChannelCreationFailed -> {
+ buf.putInt(14)
+ Unit
+ }
+ is NodeException.ChannelClosingFailed -> {
+ buf.putInt(15)
+ Unit
+ }
+ is NodeException.PersistenceFailed -> {
+ buf.putInt(16)
+ Unit
+ }
+ is NodeException.WalletOperationFailed -> {
+ buf.putInt(17)
+ Unit
+ }
+ is NodeException.WalletSigningFailed -> {
+ buf.putInt(18)
+ Unit
+ }
+ is NodeException.TxSyncFailed -> {
+ buf.putInt(19)
+ Unit
+ }
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+
+}
+
+
+
+
+public object FfiConverterOptionalULong: FfiConverterRustBuffer {
+ override fun read(buf: ByteBuffer): ULong? {
+ if (buf.get().toInt() == 0) {
+ return null
+ }
+ return FfiConverterULong.read(buf)
+ }
+
+ override fun allocationSize(value: ULong?): Int {
+ if (value == null) {
+ return 1
+ } else {
+ return 1 + FfiConverterULong.allocationSize(value)
+ }
+ }
+
+ override fun write(value: ULong?, buf: ByteBuffer) {
+ if (value == null) {
+ buf.put(0)
+ } else {
+ buf.put(1)
+ FfiConverterULong.write(value, buf)
+ }
+ }
+}
+
+
+
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias Address = String
+public typealias FfiConverterTypeAddress = FfiConverterString
+
+
+
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias ChannelId = String
+public typealias FfiConverterTypeChannelId = FfiConverterString
+
+
+
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias Invoice = String
+public typealias FfiConverterTypeInvoice = FfiConverterString
+
+
+
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias PaymentHash = String
+public typealias FfiConverterTypePaymentHash = FfiConverterString
+
+
+
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias PublicKey = String
+public typealias FfiConverterTypePublicKey = FfiConverterString
+
+
+
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias UserChannelId = String
+public typealias FfiConverterTypeUserChannelId = FfiConverterString
+
+
diff --git a/uniffi_bindgen_generate_kotlin.sh b/uniffi_bindgen_generate_kotlin.sh
new file mode 100644
index 000000000..398a5406c
--- /dev/null
+++ b/uniffi_bindgen_generate_kotlin.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+# make sure you're using the uniffi-bindgen tool version 0.21.0
+
+# 1. Generate the glue file
+# 2. Build the native binaries
+# 3. Move them both in the jvm lib
+uniffi-bindgen generate uniffi/ldk_node.udl --language kotlin
+cargo +nightly build --target aarch64-apple-darwin
+cp ./uniffi/uniffi/ldk_node/ldk_node.kt ./ldk-node-jvm/lib/src/main/kotlin/ldk/node/
+cp ./target/debug/libldk_node.dylib ./ldk-node-jvm/lib/src/main/resources/darwin-aarch64/libuniffi_ldk_node.dylib