[WIP] reorganize and remove old stuff
This commit is contained in:
parent
a7f7ab0ef9
commit
290db0105f
2
.gitignore
vendored
2
.gitignore
vendored
@ -7,4 +7,4 @@ wallet_db
|
|||||||
bdk_ffi_test
|
bdk_ffi_test
|
||||||
local.properties
|
local.properties
|
||||||
*.log
|
*.log
|
||||||
targets/kotlin/testdb
|
*.dylib
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "uniffi_bdk"
|
name = "uniffi_bdk"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Steve Myers <steve@notmandatory.org>"]
|
authors = ["Steve Myers <steve@notmandatory.org>", ""]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
@ -3,9 +3,9 @@ UniFFI
|
|||||||
|
|
||||||
1. cargo install uniffi_bindgen
|
1. cargo install uniffi_bindgen
|
||||||
2. cargo build
|
2. cargo build
|
||||||
3. uniffi-bindgen generate --no-format --out-dir targets/kotlin/src/main/kotlin src/bdk.udl --language kotlin
|
3. uniffi-bindgen generate --no-format --out-dir bindings/bdk-kotlin/src/main/kotlin src/bdk.udl --language kotlin
|
||||||
4. cp target/debug/libuniffi_bdk.dylib targets/kotlin/src/main/resources/darwin-x86-64
|
4. cp target/debug/libuniffi_bdk.dylib bindings/bdk-kotlin/src/main/resources/darwin-x86-64
|
||||||
5. gradle build -Djna.debug_load=true -Djna.debug_load.jna
|
5. cd bindings/bdk-kotlin; gradle build -Djna.debug_load=true -Djna.debug_load.jna
|
||||||
|
|
||||||
|
|
||||||
Setup Android build environment
|
Setup Android build environment
|
||||||
|
7
bdk-kotlin/.gitignore
vendored
7
bdk-kotlin/.gitignore
vendored
@ -1,7 +0,0 @@
|
|||||||
/target
|
|
||||||
.idea
|
|
||||||
.gradle
|
|
||||||
local.properties
|
|
||||||
build
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
@ -1,71 +0,0 @@
|
|||||||
apply plugin: 'com.android.library'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
apply plugin: 'maven-publish'
|
|
||||||
|
|
||||||
android {
|
|
||||||
compileSdkVersion 30
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdkVersion 21
|
|
||||||
targetSdkVersion 30
|
|
||||||
versionCode 1
|
|
||||||
versionName "1.0"
|
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
consumerProguardFiles 'consumer-rules.pro'
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
minifyEnabled false
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
afterEvaluate {
|
|
||||||
|
|
||||||
publishing {
|
|
||||||
publications {
|
|
||||||
// Creates a Maven publication called "release".
|
|
||||||
release(MavenPublication) {
|
|
||||||
// Applies the component for the release build variant.
|
|
||||||
from components.release
|
|
||||||
|
|
||||||
// You can then customize attributes of the publication as shown below.
|
|
||||||
groupId = 'org.bitcoindevkit'
|
|
||||||
artifactId = 'bdk'
|
|
||||||
version = '0.0.1-SNAPSHOT'
|
|
||||||
}
|
|
||||||
// Creates a Maven publication called “debug”.
|
|
||||||
debug(MavenPublication) {
|
|
||||||
// Applies the component for the debug build variant.
|
|
||||||
from components.debug
|
|
||||||
|
|
||||||
groupId = 'org.bitcoindevkit'
|
|
||||||
artifactId = 'bdk-debug'
|
|
||||||
version = '0.0.1-SNAPSHOT'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(project(':jvm')) {
|
|
||||||
exclude group: 'net.java.dev.jna', module: 'jna'
|
|
||||||
}
|
|
||||||
|
|
||||||
implementation 'net.java.dev.jna:jna:5.8.0@aar'
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.3.0'
|
|
||||||
implementation 'androidx.core:core-ktx:1.5.0'
|
|
||||||
api "org.slf4j:slf4j-api:1.7.30"
|
|
||||||
|
|
||||||
androidTestImplementation 'com.github.tony19:logback-android:2.0.0'
|
|
||||||
androidTestImplementation(project(':test-fixtures')) {
|
|
||||||
exclude group: 'net.java.dev.jna', module: 'jna'
|
|
||||||
}
|
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
|
||||||
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1'
|
|
||||||
}
|
|
26
bdk-kotlin/android/proguard-rules.pro
vendored
26
bdk-kotlin/android/proguard-rules.pro
vendored
@ -1,26 +0,0 @@
|
|||||||
# Add project specific ProGuard rules here.
|
|
||||||
# You can control the set of applied configuration files using the
|
|
||||||
# proguardFiles setting in build.gradle.
|
|
||||||
#
|
|
||||||
# For more details, see
|
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
||||||
|
|
||||||
# If your project uses WebView with JS, uncomment the following
|
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
|
||||||
# class:
|
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
||||||
# public *;
|
|
||||||
#}
|
|
||||||
|
|
||||||
# Uncomment this to preserve the line number information for
|
|
||||||
# debugging stack traces.
|
|
||||||
#-keepattributes SourceFile,LineNumberTable
|
|
||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
|
||||||
# hide the original source file name.
|
|
||||||
#-renamesourcefileattribute SourceFile
|
|
||||||
|
|
||||||
# for JNA
|
|
||||||
-dontwarn java.awt.*
|
|
||||||
-keep class com.sun.jna.* { *; }
|
|
||||||
-keepclassmembers class * extends com.sun.jna.* { public *; }
|
|
@ -1,14 +0,0 @@
|
|||||||
<configuration>
|
|
||||||
<appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender">
|
|
||||||
<tagEncoder>
|
|
||||||
<pattern>%logger{12}</pattern>
|
|
||||||
</tagEncoder>
|
|
||||||
<encoder>
|
|
||||||
<pattern>[%-20thread] %msg</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<root level="DEBUG">
|
|
||||||
<appender-ref ref="logcat" />
|
|
||||||
</root>
|
|
||||||
</configuration>
|
|
@ -1,21 +0,0 @@
|
|||||||
package org.bitcoindevkit.bdk
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.Context.MODE_PRIVATE
|
|
||||||
import androidx.test.core.app.ApplicationProvider
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class AndroidLibTest : LibTest() {
|
|
||||||
override fun getTestDataDir(): String {
|
|
||||||
val context = ApplicationProvider.getApplicationContext<Application>()
|
|
||||||
return context.getDir("bdk-test", MODE_PRIVATE).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
package="org.bitcoindevkit.bdk">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
|
|
||||||
</manifest>
|
|
@ -1,22 +0,0 @@
|
|||||||
buildscript {
|
|
||||||
ext.kotlin_version = '1.5.10'
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:4.2.1'
|
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allprojects {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task clean(type: Delete) {
|
|
||||||
delete rootProject.buildDir
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'org.jetbrains.kotlin.jvm'
|
|
||||||
id 'java-library'
|
|
||||||
id 'maven-publish'
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
testLogging {
|
|
||||||
events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation platform('org.jetbrains.kotlin:kotlin-bom')
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
|
||||||
implementation "net.java.dev.jna:jna:5.8.0"
|
|
||||||
api "org.slf4j:slf4j-api:1.7.30"
|
|
||||||
testImplementation "ch.qos.logback:logback-classic:1.2.3"
|
|
||||||
testImplementation "ch.qos.logback:logback-core:1.2.3"
|
|
||||||
testImplementation(project(':test-fixtures'))
|
|
||||||
}
|
|
||||||
|
|
||||||
publishing {
|
|
||||||
publications {
|
|
||||||
maven(MavenPublication) {
|
|
||||||
groupId = 'org.bitcoindevkit'
|
|
||||||
artifactId = 'bdk'
|
|
||||||
version = '0.0.1-SNAPSHOT'
|
|
||||||
|
|
||||||
from components.java
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package org.bitcoindevkit.bdk
|
|
||||||
|
|
||||||
enum class FfiError {
|
|
||||||
None,
|
|
||||||
InvalidU32Bytes,
|
|
||||||
Generic,
|
|
||||||
ScriptDoesntHaveAddressForm,
|
|
||||||
SingleRecipientMultipleOutputs,
|
|
||||||
SingleRecipientNoInputs,
|
|
||||||
NoRecipients,
|
|
||||||
NoUtxosSelected,
|
|
||||||
OutputBelowDustLimit,
|
|
||||||
InsufficientFunds,
|
|
||||||
BnBTotalTriesExceeded,
|
|
||||||
BnBNoExactMatch,
|
|
||||||
UnknownUtxo,
|
|
||||||
TransactionNotFound,
|
|
||||||
TransactionConfirmed,
|
|
||||||
IrreplaceableTransaction,
|
|
||||||
FeeRateTooLow,
|
|
||||||
FeeTooLow,
|
|
||||||
MissingKeyOrigin,
|
|
||||||
Key,
|
|
||||||
ChecksumMismatch,
|
|
||||||
SpendingPolicyRequired,
|
|
||||||
InvalidPolicyPathError,
|
|
||||||
Signer,
|
|
||||||
InvalidProgressValue,
|
|
||||||
ProgressUpdateError,
|
|
||||||
InvalidOutpoint,
|
|
||||||
Descriptor,
|
|
||||||
AddressValidator,
|
|
||||||
Encode,
|
|
||||||
Miniscript,
|
|
||||||
Bip32,
|
|
||||||
Secp256k1,
|
|
||||||
Json,
|
|
||||||
Hex,
|
|
||||||
Psbt,
|
|
||||||
Electrum,
|
|
||||||
|
|
||||||
// Esplora
|
|
||||||
// CompactFilters
|
|
||||||
Sled,
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package org.bitcoindevkit.bdk
|
|
||||||
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
|
|
||||||
class FfiException(val err: FfiError) : Exception() {
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(FfiException::class.java)
|
|
||||||
|
|
||||||
init {
|
|
||||||
log.error("JnaError: [{}] {}",err.ordinal, err.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal constructor(err: Short) : this(FfiError.values()[err.toInt()])
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package org.bitcoindevkit.bdk
|
|
||||||
|
|
||||||
import com.sun.jna.Native
|
|
||||||
|
|
||||||
abstract class LibBase {
|
|
||||||
|
|
||||||
protected val libJna: LibJna
|
|
||||||
get() = Native.load("bdk_ffi", LibJna::class.java)
|
|
||||||
}
|
|
@ -1,407 +0,0 @@
|
|||||||
package org.bitcoindevkit.bdk
|
|
||||||
|
|
||||||
import com.sun.jna.*
|
|
||||||
|
|
||||||
interface LibJna : Library {
|
|
||||||
|
|
||||||
// typedef struct {
|
|
||||||
//
|
|
||||||
// char * ok;
|
|
||||||
//
|
|
||||||
// FfiError_t err;
|
|
||||||
//
|
|
||||||
//} FfiResult_char_ptr_t;
|
|
||||||
open class FfiResult_char_ptr_t : Structure() {
|
|
||||||
class ByValue : FfiResult_char_ptr_t(), Structure.ByValue
|
|
||||||
class ByReference : FfiResult_char_ptr_t(), Structure.ByReference
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var ok: String? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var err: Short? = null
|
|
||||||
|
|
||||||
override fun getFieldOrder() = listOf("ok", "err")
|
|
||||||
}
|
|
||||||
|
|
||||||
// void free_string_result (
|
|
||||||
// FfiResult_char_ptr_t string_result);
|
|
||||||
fun free_string_result(string_result: FfiResult_char_ptr_t.ByValue)
|
|
||||||
|
|
||||||
// typedef struct {
|
|
||||||
//
|
|
||||||
// FfiError_t err;
|
|
||||||
//
|
|
||||||
//} FfiResultVoid_t;
|
|
||||||
open class FfiResultVoid_t : Structure() {
|
|
||||||
class ByValue : FfiResultVoid_t(), Structure.ByValue
|
|
||||||
class ByReference : FfiResultVoid_t(), Structure.ByReference
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var err: Short? = null
|
|
||||||
|
|
||||||
override fun getFieldOrder() = listOf("err")
|
|
||||||
}
|
|
||||||
|
|
||||||
// void free_void_result (
|
|
||||||
// FfiResultVoid_t void_result);
|
|
||||||
fun free_void_result(void_result: FfiResultVoid_t.ByValue)
|
|
||||||
|
|
||||||
// void free_string (
|
|
||||||
// char * string);
|
|
||||||
fun free_string(string: Pointer?)
|
|
||||||
|
|
||||||
// typedef struct BlockchainConfig BlockchainConfig_t;
|
|
||||||
class BlockchainConfig_t : PointerType {
|
|
||||||
constructor() : super()
|
|
||||||
constructor(pointer: Pointer) : super(pointer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockchainConfig_t * new_electrum_config (
|
|
||||||
// char const * url,
|
|
||||||
// char const * socks5,
|
|
||||||
// int16_t retry,
|
|
||||||
// int16_t timeout,
|
|
||||||
// size_t stop_gap);
|
|
||||||
fun new_electrum_config(
|
|
||||||
url: String,
|
|
||||||
socks5: String?,
|
|
||||||
retry: Short,
|
|
||||||
timeout: Short,
|
|
||||||
stop_gap: Long,
|
|
||||||
): BlockchainConfig_t
|
|
||||||
|
|
||||||
// void free_blockchain_config (
|
|
||||||
// BlockchainConfig_t * blockchain_config);
|
|
||||||
fun free_blockchain_config(blockchain_config: BlockchainConfig_t)
|
|
||||||
|
|
||||||
// typedef struct DatabaseConfig DatabaseConfig_t;
|
|
||||||
class DatabaseConfig_t : PointerType {
|
|
||||||
constructor() : super()
|
|
||||||
constructor(pointer: Pointer) : super(pointer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// typedef struct OpaqueWallet OpaqueWallet_t;
|
|
||||||
class OpaqueWallet_t : PointerType {
|
|
||||||
constructor() : super()
|
|
||||||
constructor(pointer: Pointer) : super(pointer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// typedef struct {
|
|
||||||
//
|
|
||||||
// OpaqueWallet_t * ok;
|
|
||||||
//
|
|
||||||
// FfiError_t err;
|
|
||||||
//
|
|
||||||
// } FfiResult_OpaqueWallet_ptr_t;
|
|
||||||
open class FfiResult_OpaqueWallet_ptr_t : Structure() {
|
|
||||||
class ByValue : FfiResult_OpaqueWallet_ptr_t(), Structure.ByValue
|
|
||||||
class ByReference : FfiResult_OpaqueWallet_ptr_t(), Structure.ByReference
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var ok: OpaqueWallet_t? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var err: Short? = null
|
|
||||||
|
|
||||||
override fun getFieldOrder() = listOf("ok", "err")
|
|
||||||
}
|
|
||||||
|
|
||||||
// FfiResult_OpaqueWallet_ptr_t new_wallet_result (
|
|
||||||
// char const * descriptor,
|
|
||||||
// char const * change_descriptor,
|
|
||||||
// char const * network,
|
|
||||||
// BlockchainConfig_t const * blockchain_config,
|
|
||||||
// DatabaseConfig_t const * database_config);
|
|
||||||
fun new_wallet_result(
|
|
||||||
descriptor: String,
|
|
||||||
changeDescriptor: String?,
|
|
||||||
network: String,
|
|
||||||
blockchainConfig: BlockchainConfig_t,
|
|
||||||
databaseConfig: DatabaseConfig_t,
|
|
||||||
): FfiResult_OpaqueWallet_ptr_t.ByValue
|
|
||||||
|
|
||||||
// void free_wallet_result (
|
|
||||||
// FfiResult_OpaqueWallet_ptr_t wallet_result);
|
|
||||||
fun free_wallet_result(wallet_result: FfiResult_OpaqueWallet_ptr_t.ByValue)
|
|
||||||
|
|
||||||
// typedef struct {
|
|
||||||
//
|
|
||||||
// char * txid;
|
|
||||||
//
|
|
||||||
// uint32_t vout;
|
|
||||||
//
|
|
||||||
// } OutPoint_t;
|
|
||||||
open class OutPoint_t : Structure() {
|
|
||||||
class ByValue : OutPoint_t(), Structure.ByValue
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var txid: String? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var vout: Int? = null
|
|
||||||
|
|
||||||
override fun getFieldOrder() = listOf("txid", "vout")
|
|
||||||
}
|
|
||||||
|
|
||||||
// typedef struct {
|
|
||||||
//
|
|
||||||
// uint64_t value;
|
|
||||||
//
|
|
||||||
// char * script_pubkey;
|
|
||||||
//
|
|
||||||
// } TxOut_t;
|
|
||||||
open class TxOut_t : Structure() {
|
|
||||||
class ByValue : TxOut_t(), Structure.ByValue
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var value: Long? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var script_pubkey: String? = null
|
|
||||||
|
|
||||||
override fun getFieldOrder() = listOf("value", "script_pubkey")
|
|
||||||
}
|
|
||||||
|
|
||||||
// typedef struct {
|
|
||||||
//
|
|
||||||
// OutPoint_t outpoint;
|
|
||||||
//
|
|
||||||
// TxOut_t txout;
|
|
||||||
//
|
|
||||||
// uint16_t keychain;
|
|
||||||
//
|
|
||||||
// } LocalUtxo_t;
|
|
||||||
open class LocalUtxo_t : Structure() {
|
|
||||||
|
|
||||||
class ByValue : LocalUtxo_t(), Structure.ByValue
|
|
||||||
class ByReference : LocalUtxo_t(), Structure.ByReference
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var outpoint: OutPoint_t? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var txout: TxOut_t? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var keychain: Short? = null
|
|
||||||
|
|
||||||
override fun getFieldOrder() = listOf("outpoint", "txout", "keychain")
|
|
||||||
}
|
|
||||||
|
|
||||||
// typedef struct {
|
|
||||||
//
|
|
||||||
// LocalUtxo_t * ptr;
|
|
||||||
//
|
|
||||||
// size_t len;
|
|
||||||
//
|
|
||||||
// size_t cap;
|
|
||||||
//
|
|
||||||
// } Vec_LocalUtxo_t;
|
|
||||||
open class Vec_LocalUtxo_t : Structure() {
|
|
||||||
|
|
||||||
class ByReference : Vec_LocalUtxo_t(), Structure.ByReference
|
|
||||||
class ByValue : Vec_LocalUtxo_t(), Structure.ByValue
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var ptr: LocalUtxo_t.ByReference? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var len: NativeLong? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var cap: NativeLong? = null
|
|
||||||
|
|
||||||
override fun getFieldOrder() = listOf("ptr", "len", "cap")
|
|
||||||
}
|
|
||||||
|
|
||||||
// typedef struct {
|
|
||||||
//
|
|
||||||
// Vec_LocalUtxo_t ok;
|
|
||||||
//
|
|
||||||
// FfiError_t err;
|
|
||||||
//
|
|
||||||
// } FfiResult_Vec_LocalUtxo_t;
|
|
||||||
open class FfiResultVec_LocalUtxo_t : Structure() {
|
|
||||||
|
|
||||||
class ByValue : FfiResultVec_LocalUtxo_t(), Structure.ByValue
|
|
||||||
class ByReference : FfiResultVec_LocalUtxo_t(), Structure.ByReference
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var ok: Vec_LocalUtxo_t? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var err: Short? = null
|
|
||||||
|
|
||||||
override fun getFieldOrder() = listOf("ok", "err")
|
|
||||||
}
|
|
||||||
|
|
||||||
// void free_veclocalutxo_result (
|
|
||||||
// FfiResult_Vec_LocalUtxo_t unspent_result);
|
|
||||||
fun free_veclocalutxo_result(unspent_result: FfiResultVec_LocalUtxo_t.ByValue)
|
|
||||||
|
|
||||||
// typedef struct {
|
|
||||||
//
|
|
||||||
// uint64_t ok;
|
|
||||||
//
|
|
||||||
// FfiError_t err;
|
|
||||||
//
|
|
||||||
// } FfiResult_uint64_t;
|
|
||||||
open class FfiResult_uint64_t : Structure() {
|
|
||||||
|
|
||||||
class ByValue : FfiResult_uint64_t(), Structure.ByValue
|
|
||||||
class ByReference : FfiResult_uint64_t(), Structure.ByReference
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var ok: Long? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var err: Short? = null
|
|
||||||
|
|
||||||
override fun getFieldOrder() = listOf("ok", "err")
|
|
||||||
}
|
|
||||||
|
|
||||||
// void free_uint64_result (
|
|
||||||
// FfiResult_uint64_t void_result);
|
|
||||||
fun free_uint64_result(unspent_result: FfiResult_uint64_t.ByValue)
|
|
||||||
|
|
||||||
// FfiResultVoid_t sync_wallet (
|
|
||||||
// OpaqueWallet_t const * opaque_wallet);
|
|
||||||
fun sync_wallet(opaque_wallet: OpaqueWallet_t): FfiResultVoid_t.ByValue
|
|
||||||
|
|
||||||
// FfiResult_char_ptr_t new_address (
|
|
||||||
// OpaqueWallet_t const * opaque_wallet);
|
|
||||||
fun new_address(opaque_wallet: OpaqueWallet_t): FfiResult_char_ptr_t.ByValue
|
|
||||||
|
|
||||||
// FfiResult_Vec_LocalUtxo_t list_unspent (
|
|
||||||
// OpaqueWallet_t const * opaque_wallet);
|
|
||||||
fun list_unspent(opaque_wallet: OpaqueWallet_t): FfiResultVec_LocalUtxo_t.ByValue
|
|
||||||
|
|
||||||
// FfiResult_uint64_t balance (
|
|
||||||
// OpaqueWallet_t const * opaque_wallet);
|
|
||||||
fun balance(opaque_wallet: OpaqueWallet_t): FfiResult_uint64_t.ByValue
|
|
||||||
|
|
||||||
// DatabaseConfig_t * new_memory_config (void);
|
|
||||||
fun new_memory_config(): DatabaseConfig_t
|
|
||||||
|
|
||||||
// DatabaseConfig_t * new_sled_config (
|
|
||||||
// char const * path,
|
|
||||||
// char const * tree_name);
|
|
||||||
fun new_sled_config(path: String, tree_name: String): DatabaseConfig_t
|
|
||||||
|
|
||||||
// void free_database_config (
|
|
||||||
// DatabaseConfig_t * database_config);
|
|
||||||
fun free_database_config(database_config: DatabaseConfig_t)
|
|
||||||
|
|
||||||
// typedef struct {
|
|
||||||
// uint32_t height;
|
|
||||||
// uint64_t timestamp;
|
|
||||||
// } ConfirmationTime_t;
|
|
||||||
open class ConfirmationTime_t : Structure() {
|
|
||||||
|
|
||||||
class ByValue : ConfirmationTime_t(), Structure.ByValue
|
|
||||||
class ByReference : ConfirmationTime_t(), Structure.ByReference
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var height: Int? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var timestamp: Long? = null
|
|
||||||
|
|
||||||
override fun getFieldOrder() = listOf("height", "timestamp")
|
|
||||||
}
|
|
||||||
|
|
||||||
// typedef struct {
|
|
||||||
// char * txid;
|
|
||||||
// uint64_t received;
|
|
||||||
// uint64_t sent;
|
|
||||||
// int64_t fee;
|
|
||||||
// bool is_confirmed;
|
|
||||||
// ConfirmationTime_t confirmation_time;
|
|
||||||
// bool verified;
|
|
||||||
// } TransactionDetails_t;
|
|
||||||
open class TransactionDetails_t : Structure() {
|
|
||||||
|
|
||||||
class ByValue : TransactionDetails_t(), Structure.ByValue
|
|
||||||
class ByReference : TransactionDetails_t(), Structure.ByReference
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var txid: String? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var received: Long? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var sent: Long? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var fee: Long? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var is_confirmed: Boolean? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var confirmation_time: ConfirmationTime_t? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var verified: Boolean? = null
|
|
||||||
|
|
||||||
override fun getFieldOrder() = listOf("txid", "received", "sent", "fee", "is_confirmed", "confirmation_time", "verified")
|
|
||||||
}
|
|
||||||
|
|
||||||
// typedef struct {
|
|
||||||
//
|
|
||||||
// TransactionDetails_t * ptr;
|
|
||||||
//
|
|
||||||
// size_t len;
|
|
||||||
//
|
|
||||||
// size_t cap;
|
|
||||||
//
|
|
||||||
// } Vec_TransactionDetails_t;
|
|
||||||
open class Vec_TransactionDetails_t : Structure() {
|
|
||||||
|
|
||||||
class ByReference : Vec_TransactionDetails_t(), Structure.ByReference
|
|
||||||
class ByValue : Vec_TransactionDetails_t(), Structure.ByValue
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var ptr: TransactionDetails_t.ByReference? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var len: NativeLong? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var cap: NativeLong? = null
|
|
||||||
|
|
||||||
override fun getFieldOrder() = listOf("ptr", "len", "cap")
|
|
||||||
}
|
|
||||||
|
|
||||||
// typedef struct {
|
|
||||||
//
|
|
||||||
// Vec_TransactionDetails_t ok;
|
|
||||||
//
|
|
||||||
// FfiError_t err;
|
|
||||||
//
|
|
||||||
// } FfiResult_Vec_TransactionDetails_t;
|
|
||||||
open class FfiResult_Vec_TransactionDetails_t : Structure() {
|
|
||||||
|
|
||||||
class ByValue : FfiResult_Vec_TransactionDetails_t(), Structure.ByValue
|
|
||||||
class ByReference : FfiResult_Vec_TransactionDetails_t(), Structure.ByReference
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var ok: Vec_TransactionDetails_t? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var err: Short? = null
|
|
||||||
|
|
||||||
override fun getFieldOrder() = listOf("ok", "err")
|
|
||||||
}
|
|
||||||
|
|
||||||
// FfiResult_Vec_TransactionDetails_t list_transactions (
|
|
||||||
// OpaqueWallet_t const * opaque_wallet);
|
|
||||||
fun list_transactions(opaque_wallet: OpaqueWallet_t): FfiResult_Vec_TransactionDetails_t.ByValue
|
|
||||||
|
|
||||||
|
|
||||||
// void free_vectxdetails_result (
|
|
||||||
// FfiResult_Vec_TransactionDetails_t txdetails_result);
|
|
||||||
fun free_vectxdetails_result(txdetails_result: FfiResult_Vec_TransactionDetails_t.ByValue)
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package org.bitcoindevkit.bdk.types
|
|
||||||
|
|
||||||
import org.bitcoindevkit.bdk.FfiException
|
|
||||||
import org.bitcoindevkit.bdk.LibBase
|
|
||||||
import org.bitcoindevkit.bdk.LibJna
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
|
|
||||||
class StringResult constructor(private val ffiResultCharPtrT: LibJna.FfiResult_char_ptr_t.ByValue) :
|
|
||||||
LibBase() {
|
|
||||||
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(StringResult::class.java)
|
|
||||||
|
|
||||||
fun value(): String {
|
|
||||||
val err = ffiResultCharPtrT.err!!
|
|
||||||
val ok = ffiResultCharPtrT.ok!!
|
|
||||||
when {
|
|
||||||
err > 0 -> {
|
|
||||||
throw FfiException(err)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun finalize() {
|
|
||||||
libJna.free_string_result(ffiResultCharPtrT)
|
|
||||||
log.debug("$ffiResultCharPtrT freed")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package org.bitcoindevkit.bdk.types
|
|
||||||
|
|
||||||
import org.bitcoindevkit.bdk.FfiException
|
|
||||||
import org.bitcoindevkit.bdk.LibBase
|
|
||||||
import org.bitcoindevkit.bdk.LibJna
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
|
|
||||||
class UInt64Result constructor(private val ffiResultUint64T: LibJna.FfiResult_uint64_t.ByValue) :
|
|
||||||
LibBase() {
|
|
||||||
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(UInt64Result::class.java)
|
|
||||||
|
|
||||||
fun value(): Long {
|
|
||||||
val err = ffiResultUint64T.err!!
|
|
||||||
val ok = ffiResultUint64T.ok!!
|
|
||||||
when {
|
|
||||||
err > 0 -> {
|
|
||||||
throw FfiException(err)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun finalize() {
|
|
||||||
libJna.free_uint64_result(ffiResultUint64T)
|
|
||||||
log.debug("$ffiResultUint64T freed")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package org.bitcoindevkit.bdk.types
|
|
||||||
|
|
||||||
import org.bitcoindevkit.bdk.FfiException
|
|
||||||
import org.bitcoindevkit.bdk.LibBase
|
|
||||||
import org.bitcoindevkit.bdk.LibJna
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
|
|
||||||
class VoidResult constructor(private val ffiResultVoidT: LibJna.FfiResultVoid_t.ByValue) :
|
|
||||||
LibBase() {
|
|
||||||
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(VoidResult::class.java)
|
|
||||||
|
|
||||||
fun value(): Unit {
|
|
||||||
val err = ffiResultVoidT.err!!
|
|
||||||
|
|
||||||
when {
|
|
||||||
err > 0 -> {
|
|
||||||
throw FfiException(err)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun finalize() {
|
|
||||||
libJna.free_void_result(ffiResultVoidT)
|
|
||||||
log.debug("$ffiResultVoidT freed")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package org.bitcoindevkit.bdk
|
|
||||||
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
|
|
||||||
abstract class BlockchainConfig : LibBase() {
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(BlockchainConfig::class.java)
|
|
||||||
abstract val blockchainConfigT: LibJna.BlockchainConfig_t
|
|
||||||
|
|
||||||
protected fun finalize() {
|
|
||||||
libJna.free_blockchain_config(blockchainConfigT)
|
|
||||||
log.debug("$blockchainConfigT freed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ElectrumConfig(
|
|
||||||
url: String,
|
|
||||||
socks5: String?,
|
|
||||||
retry: Short,
|
|
||||||
timeout: Short,
|
|
||||||
stopGap: Long,
|
|
||||||
) : BlockchainConfig() {
|
|
||||||
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(ElectrumConfig::class.java)
|
|
||||||
override val blockchainConfigT = libJna.new_electrum_config(url, socks5, retry, timeout, stopGap)
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package org.bitcoindevkit.bdk
|
|
||||||
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
|
|
||||||
abstract class DatabaseConfig : LibBase() {
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(DatabaseConfig::class.java)
|
|
||||||
abstract val databaseConfigT: LibJna.DatabaseConfig_t
|
|
||||||
|
|
||||||
protected fun finalize() {
|
|
||||||
libJna.free_database_config(databaseConfigT)
|
|
||||||
log.debug("$databaseConfigT freed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MemoryConfig : DatabaseConfig() {
|
|
||||||
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(MemoryConfig::class.java)
|
|
||||||
override val databaseConfigT = libJna.new_memory_config()
|
|
||||||
}
|
|
||||||
|
|
||||||
class SledConfig(path: String, treeName: String) : DatabaseConfig() {
|
|
||||||
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(SledConfig::class.java)
|
|
||||||
override val databaseConfigT = libJna.new_sled_config(path, treeName)
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package org.bitcoindevkit.bdk.wallet
|
|
||||||
|
|
||||||
import org.bitcoindevkit.bdk.FfiException
|
|
||||||
import org.bitcoindevkit.bdk.LibBase
|
|
||||||
import org.bitcoindevkit.bdk.LibJna
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
|
|
||||||
class VecLocalUtxoResult(private val ffiResultVecLocalUtxoT: LibJna.FfiResultVec_LocalUtxo_t.ByValue) :
|
|
||||||
LibBase() {
|
|
||||||
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(VecLocalUtxoResult::class.java)
|
|
||||||
|
|
||||||
fun value(): Array<LibJna.LocalUtxo_t.ByReference> {
|
|
||||||
val err = ffiResultVecLocalUtxoT.err!!
|
|
||||||
val ok = ffiResultVecLocalUtxoT.ok!!
|
|
||||||
when {
|
|
||||||
err > 0 -> {
|
|
||||||
throw FfiException(err)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val first = ok.ptr!!
|
|
||||||
return first.toArray(ok.len!!.toInt()) as Array<LibJna.LocalUtxo_t.ByReference>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun finalize() {
|
|
||||||
libJna.free_veclocalutxo_result(ffiResultVecLocalUtxoT)
|
|
||||||
log.debug("$ffiResultVecLocalUtxoT freed")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package org.bitcoindevkit.bdk.wallet
|
|
||||||
|
|
||||||
import org.bitcoindevkit.bdk.FfiException
|
|
||||||
import org.bitcoindevkit.bdk.LibBase
|
|
||||||
import org.bitcoindevkit.bdk.LibJna
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
|
|
||||||
class VecTxDetailsResult(private val ffiResultVecTransactionDetailsT: LibJna.FfiResult_Vec_TransactionDetails_t.ByValue) :
|
|
||||||
LibBase() {
|
|
||||||
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(VecTxDetailsResult::class.java)
|
|
||||||
|
|
||||||
fun value(): Array<LibJna.TransactionDetails_t.ByReference> {
|
|
||||||
val err = ffiResultVecTransactionDetailsT.err!!
|
|
||||||
val ok = ffiResultVecTransactionDetailsT.ok!!
|
|
||||||
when {
|
|
||||||
err > 0 -> {
|
|
||||||
throw FfiException(err)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val first = ok.ptr!!
|
|
||||||
return first.toArray(ok.len!!.toInt()) as Array<LibJna.TransactionDetails_t.ByReference>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun finalize() {
|
|
||||||
libJna.free_vectxdetails_result(ffiResultVecTransactionDetailsT)
|
|
||||||
log.debug("$ffiResultVecTransactionDetailsT freed")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
package org.bitcoindevkit.bdk.wallet
|
|
||||||
|
|
||||||
import org.bitcoindevkit.bdk.BlockchainConfig
|
|
||||||
import org.bitcoindevkit.bdk.DatabaseConfig
|
|
||||||
import org.bitcoindevkit.bdk.LibBase
|
|
||||||
import org.bitcoindevkit.bdk.LibJna
|
|
||||||
import org.bitcoindevkit.bdk.types.StringResult
|
|
||||||
import org.bitcoindevkit.bdk.types.UInt64Result
|
|
||||||
import org.bitcoindevkit.bdk.types.VoidResult
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
|
|
||||||
enum class Network {
|
|
||||||
Bitcoin,
|
|
||||||
Testnet,
|
|
||||||
Signet,
|
|
||||||
Regtest,
|
|
||||||
}
|
|
||||||
|
|
||||||
class Wallet constructor(
|
|
||||||
descriptor: String,
|
|
||||||
changeDescriptor: String?,
|
|
||||||
network: Network,
|
|
||||||
blockchainConfig: BlockchainConfig,
|
|
||||||
databaseConfig: DatabaseConfig,
|
|
||||||
) : LibBase() {
|
|
||||||
|
|
||||||
val log: Logger = LoggerFactory.getLogger(Wallet::class.java)
|
|
||||||
|
|
||||||
private val walletResult = WalletResult(
|
|
||||||
libJna.new_wallet_result(
|
|
||||||
descriptor,
|
|
||||||
changeDescriptor,
|
|
||||||
network.toString().lowercase(),
|
|
||||||
blockchainConfig.blockchainConfigT,
|
|
||||||
databaseConfig.databaseConfigT
|
|
||||||
)
|
|
||||||
)
|
|
||||||
private val wallet = walletResult.value()
|
|
||||||
|
|
||||||
fun sync() {
|
|
||||||
val voidResult = VoidResult(libJna.sync_wallet(wallet))
|
|
||||||
return voidResult.value()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAddress(): String {
|
|
||||||
val stringResult = StringResult(libJna.new_address(wallet))
|
|
||||||
return stringResult.value()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun listUnspent(): Array<LibJna.LocalUtxo_t.ByReference> {
|
|
||||||
val vecLocalUtxoResult = VecLocalUtxoResult(libJna.list_unspent(wallet))
|
|
||||||
return vecLocalUtxoResult.value()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun balance(): Long {
|
|
||||||
val longResult = UInt64Result(libJna.balance(wallet))
|
|
||||||
return longResult.value()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun listTransactionDetails(): Array<LibJna.TransactionDetails_t.ByReference> {
|
|
||||||
val vecTxDetailsResult = VecTxDetailsResult(libJna.list_transactions((wallet)))
|
|
||||||
return vecTxDetailsResult.value()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package org.bitcoindevkit.bdk.wallet
|
|
||||||
|
|
||||||
import org.bitcoindevkit.bdk.FfiException
|
|
||||||
import org.bitcoindevkit.bdk.LibBase
|
|
||||||
import org.bitcoindevkit.bdk.LibJna
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
|
|
||||||
class WalletResult constructor(private val ffiResultOpaqueWalletPtrT: LibJna.FfiResult_OpaqueWallet_ptr_t.ByValue) :
|
|
||||||
LibBase() {
|
|
||||||
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(WalletResult::class.java)
|
|
||||||
|
|
||||||
fun value(): LibJna.OpaqueWallet_t {
|
|
||||||
val err = ffiResultOpaqueWalletPtrT.err!!
|
|
||||||
val ok = ffiResultOpaqueWalletPtrT.ok
|
|
||||||
when {
|
|
||||||
err > 0 -> {
|
|
||||||
throw FfiException(err)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
return ok!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun finalize() {
|
|
||||||
libJna.free_wallet_result(ffiResultOpaqueWalletPtrT)
|
|
||||||
log.debug("$ffiResultOpaqueWalletPtrT freed")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package org.bitcoindevkit.bdk
|
|
||||||
|
|
||||||
import java.nio.file.Paths
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Library test, which will execute on linux host.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class JvmLibTest : LibTest() {
|
|
||||||
|
|
||||||
override fun getTestDataDir(): String {
|
|
||||||
//return Files.createTempDirectory("bdk-test").toString()
|
|
||||||
return Paths.get(System.getProperty("java.io.tmpdir"), "bdk-test").toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
rootProject.name = 'bdk-kotlin'
|
|
||||||
|
|
||||||
include ':jvm', ':android', ':test-fixtures'
|
|
@ -1,19 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'org.jetbrains.kotlin.jvm'
|
|
||||||
id 'java-library'
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation platform('org.jetbrains.kotlin:kotlin-bom')
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
|
||||||
implementation "net.java.dev.jna:jna:5.8.0"
|
|
||||||
implementation(project(':jvm'))
|
|
||||||
implementation "junit:junit:4.13.2"
|
|
||||||
//implementation "org.mockito.kotlin:mockito-kotlin:3.2.0"
|
|
||||||
api "org.slf4j:slf4j-api:1.7.30"
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
@ -1,135 +0,0 @@
|
|||||||
package org.bitcoindevkit.bdk
|
|
||||||
|
|
||||||
import org.bitcoindevkit.bdk.wallet.Network
|
|
||||||
import org.bitcoindevkit.bdk.wallet.Wallet
|
|
||||||
import org.junit.Assert.*
|
|
||||||
import org.junit.Test
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Library tests which will execute for jvm and android modules.
|
|
||||||
*/
|
|
||||||
abstract class LibTest : LibBase() {
|
|
||||||
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(LibTest::class.java)
|
|
||||||
|
|
||||||
val name = "test_wallet"
|
|
||||||
val desc =
|
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
|
|
||||||
val change =
|
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"
|
|
||||||
val network = Network.Testnet
|
|
||||||
|
|
||||||
val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30, 100)
|
|
||||||
val databaseConfig = MemoryConfig()
|
|
||||||
|
|
||||||
abstract fun getTestDataDir(): String
|
|
||||||
|
|
||||||
fun cleanupTestDataDir() {
|
|
||||||
File(getTestDataDir()).deleteRecursively()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun walletResultError() {
|
|
||||||
val jnaException = assertThrows(FfiException::class.java) {
|
|
||||||
Wallet("bad", "bad", network, blockchainConfig, databaseConfig)
|
|
||||||
}
|
|
||||||
assertEquals(jnaException.err, FfiError.Descriptor)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Test
|
|
||||||
// fun walletResultFinalize() {
|
|
||||||
// run {
|
|
||||||
// val desc =
|
|
||||||
// "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
|
|
||||||
// val change =
|
|
||||||
// "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"
|
|
||||||
//
|
|
||||||
// val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30)
|
|
||||||
// val databaseConfig = MemoryConfig()
|
|
||||||
// val wallet = Wallet(desc, change, blockchainConfig, databaseConfig)
|
|
||||||
// wallet.sync()
|
|
||||||
// assertNotNull(wallet.getAddress())
|
|
||||||
// }
|
|
||||||
// System.gc()
|
|
||||||
// Thread.sleep(2000)
|
|
||||||
// // The only way to verify wallets freed is by checking the log
|
|
||||||
// }
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun walletSync() {
|
|
||||||
val blockchainConfig = ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5, 30, 100)
|
|
||||||
val testDataDir = getTestDataDir()
|
|
||||||
// log.debug("testDataDir = $testDataDir")
|
|
||||||
val databaseConfig = SledConfig(testDataDir, "steve-test")
|
|
||||||
val wallet = Wallet(desc, change, network, blockchainConfig, databaseConfig)
|
|
||||||
wallet.sync()
|
|
||||||
cleanupTestDataDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun walletNewAddress() {
|
|
||||||
val wallet = Wallet(desc, change, network, blockchainConfig, databaseConfig)
|
|
||||||
val address = wallet.getAddress()
|
|
||||||
assertNotNull(address)
|
|
||||||
// log.debug("address created from kotlin: $address")
|
|
||||||
assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun walletUnspent() {
|
|
||||||
val wallet = Wallet(desc, change, network, blockchainConfig, databaseConfig)
|
|
||||||
wallet.sync()
|
|
||||||
val unspent = wallet.listUnspent()
|
|
||||||
assertTrue(unspent.isNotEmpty())
|
|
||||||
|
|
||||||
unspent.iterator().forEach {
|
|
||||||
//log.debug("unspent.outpoint.txid: ${it.outpoint!!.txid}")
|
|
||||||
assertNotNull(it.outpoint?.txid)
|
|
||||||
//log.debug("unspent.outpoint.vout: ${it.outpoint?.vout}")
|
|
||||||
assertTrue(it.outpoint?.vout!! >= 0)
|
|
||||||
//log.debug("unspent.txout.value: ${it.txout?.value}")
|
|
||||||
assertTrue(it.txout?.value!! > 0)
|
|
||||||
//log.debug("unspent.txout.script_pubkey: ${it.txout?.script_pubkey}")
|
|
||||||
assertNotNull(it.txout?.script_pubkey)
|
|
||||||
//log.debug("unspent.keychain: ${it.keychain}")
|
|
||||||
assertTrue(it.keychain!! >= 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun walletBalance() {
|
|
||||||
val wallet = Wallet(desc, change, network, blockchainConfig, databaseConfig)
|
|
||||||
wallet.sync()
|
|
||||||
val balance = wallet.balance()
|
|
||||||
//log.debug("balance from kotlin: $balance")
|
|
||||||
assertTrue(balance > 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun walletTxDetails() {
|
|
||||||
val wallet = Wallet(desc, change, network, blockchainConfig, databaseConfig)
|
|
||||||
wallet.sync()
|
|
||||||
val txDetails = wallet.listTransactionDetails()
|
|
||||||
assertTrue(txDetails.isNotEmpty())
|
|
||||||
|
|
||||||
txDetails.iterator().forEach {
|
|
||||||
//log.debug("txDetails.txid: ${it.txid}")
|
|
||||||
assertNotNull(it.txid)
|
|
||||||
//log.debug("txDetails.received: ${it.received}")
|
|
||||||
//log.debug("txDetails.sent: ${it.sent}")
|
|
||||||
assertTrue(it.received!! > 0 || it.sent!! > 0)
|
|
||||||
//log.debug("txDetails.fee: ${it.fee}")
|
|
||||||
assertTrue(it.fee!! > 0)
|
|
||||||
//log.debug("txDetails.is_confirmed: ${it.is_confirmed}")
|
|
||||||
assertTrue(it.is_confirmed!!)
|
|
||||||
assertNotNull(it.confirmation_time!!)
|
|
||||||
//log.debug("txDetails.confirmation_time.timestamp: ${it.confirmation_time!!.timestamp}")
|
|
||||||
assertTrue(it.confirmation_time!!.timestamp!! > 0)
|
|
||||||
//log.debug("txDetails.confirmation_time.height: ${it.confirmation_time!!.height}")
|
|
||||||
assertTrue(it.confirmation_time!!.height!! > 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
7
bdk-swift/.gitignore
vendored
7
bdk-swift/.gitignore
vendored
@ -1,7 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
/.build
|
|
||||||
/Packages
|
|
||||||
/*.xcodeproj
|
|
||||||
xcuserdata/
|
|
||||||
DerivedData/
|
|
||||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
|
7
bdk-swift/bdk.swift/.gitignore
vendored
7
bdk-swift/bdk.swift/.gitignore
vendored
@ -1,7 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
/.build
|
|
||||||
/Packages
|
|
||||||
/*.xcodeproj
|
|
||||||
xcuserdata/
|
|
||||||
DerivedData/
|
|
||||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
|
@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"object": {
|
|
||||||
"pins": [
|
|
||||||
{
|
|
||||||
"package": "Clibbdkffi",
|
|
||||||
"repositoryURL": "/home/steve/git/notmandatory/Clibbdkffi",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "9c96e359a3b1e1d5c0db61125147f6ef929bf567",
|
|
||||||
"version": "0.1.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"version": 1
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
// swift-tools-version:5.5
|
|
||||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
|
||||||
|
|
||||||
import PackageDescription
|
|
||||||
|
|
||||||
let package = Package(
|
|
||||||
name: "bdk.swift",
|
|
||||||
products: [
|
|
||||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
|
||||||
.library(
|
|
||||||
name: "bdk.swift",
|
|
||||||
targets: ["bdk.swift"]),
|
|
||||||
],
|
|
||||||
dependencies: [
|
|
||||||
// Dependencies declare other packages that this package depends on.
|
|
||||||
.package(url: "../../../Clibbdkffi", from: "0.1.0"),
|
|
||||||
],
|
|
||||||
targets: [
|
|
||||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
|
||||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
|
||||||
.target(
|
|
||||||
name: "bdk.swift",
|
|
||||||
dependencies: ["Clibbdkffi"]),
|
|
||||||
.testTarget(
|
|
||||||
name: "bdk.swiftTests",
|
|
||||||
dependencies: ["bdk.swift"]),
|
|
||||||
]
|
|
||||||
)
|
|
@ -1,11 +0,0 @@
|
|||||||
# bdk.swift
|
|
||||||
|
|
||||||
To build:
|
|
||||||
```
|
|
||||||
swift build -Xlinker -L../../target/debug
|
|
||||||
```
|
|
||||||
|
|
||||||
To test:
|
|
||||||
```
|
|
||||||
swift test -Xlinker -L../../target/debug
|
|
||||||
```
|
|
@ -1,8 +0,0 @@
|
|||||||
import Clibbdkffi
|
|
||||||
|
|
||||||
public struct bdk_swift {
|
|
||||||
public private(set) var text = "Hello, World!"
|
|
||||||
|
|
||||||
public init() {
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
import XCTest
|
|
||||||
import Clibbdkffi
|
|
||||||
|
|
||||||
@testable import bdk_swift
|
|
||||||
|
|
||||||
final class bdk_swiftTests: XCTestCase {
|
|
||||||
func testExample() throws {
|
|
||||||
// This is an example of a functional test case.
|
|
||||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
|
||||||
// results.
|
|
||||||
XCTAssertEqual(bdk_swift().text, "Hello, World!")
|
|
||||||
let desc = "wpkh([bf988dd3/84'/1'/0']tpubDD7bHVspyCSvvU8qEycydF664NAX6EAPjJ77j9E614GU2zVdXgnZZo6JJjKbDT6fUn8owMN6TCP9rZMznsNEhJbpkEwp6fAyyoSqy3DH2Qj/0/*)";
|
|
||||||
let change = "wpkh([bf988dd3/84'/1'/0']tpubDD7bHVspyCSvvU8qEycydF664NAX6EAPjJ77j9E614GU2zVdXgnZZo6JJjKbDT6fUn8owMN6TCP9rZMznsNEhJbpkEwp6fAyyoSqy3DH2Qj/1/*)";
|
|
||||||
let net = "testnet";
|
|
||||||
let blocks = "ssl://electrum.blockstream.info:60002";
|
|
||||||
|
|
||||||
let bc_config = new_electrum_config(blocks, nil, 5, 30, 100)
|
|
||||||
let db_config = new_memory_config()
|
|
||||||
|
|
||||||
let wallet_result = new_wallet_result(desc,change,net,bc_config,db_config)
|
|
||||||
|
|
||||||
free_blockchain_config(bc_config)
|
|
||||||
free_database_config(db_config)
|
|
||||||
|
|
||||||
let wallet = wallet_result.ok
|
|
||||||
let sync_result = sync_wallet(wallet)
|
|
||||||
assert(sync_result.err == FFI_ERROR_NONE)
|
|
||||||
free_void_result(sync_result)
|
|
||||||
|
|
||||||
let address1_result = new_address(wallet).ok
|
|
||||||
let address1 = String(cString: address1_result!, encoding: .utf8)
|
|
||||||
//print("address1 = \(address1!)")
|
|
||||||
assert(address1! == "tb1qh4ajvhz9nd76tqddnl99l89hx4dat33hrjauzw")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
@ -19,7 +19,7 @@ class LibTest {
|
|||||||
val address = wallet.getNewAddress()
|
val address = wallet.getNewAddress()
|
||||||
println("address:" + address)
|
println("address:" + address)
|
||||||
assertNotNull(address)
|
assertNotNull(address)
|
||||||
// log.debug("address created from kotlin: $address")
|
// log.debug("address created from bdk-kotlin: $address")
|
||||||
assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs")
|
assertEquals(address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs")
|
||||||
}
|
}
|
||||||
}
|
}
|
28
build.sh
28
build.sh
@ -32,27 +32,27 @@ build_cc() {
|
|||||||
cc cc/bdk_ffi_test.c -o cc/bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m
|
cc cc/bdk_ffi_test.c -o cc/bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m
|
||||||
}
|
}
|
||||||
|
|
||||||
## copy to bdk-kotlin
|
## copy to bdk-bdk-kotlin
|
||||||
copy_lib_kotlin() {
|
copy_lib_kotlin() {
|
||||||
echo -n "Copy "
|
echo -n "Copy "
|
||||||
case $OS in
|
case $OS in
|
||||||
"Darwin")
|
"Darwin")
|
||||||
echo -n "darwin "
|
echo -n "darwin "
|
||||||
mkdir -p bdk-kotlin/jvm/src/main/resources/darwin-x86-64
|
mkdir -p bdk-bdk-kotlin/jvm/src/main/resources/darwin-x86-64
|
||||||
cp target/debug/libbdk_ffi.dylib bdk-kotlin/jvm/src/main/resources/darwin-x86-64
|
cp target/debug/libbdk_ffi.dylib bdk-bdk-kotlin/jvm/src/main/resources/darwin-x86-64
|
||||||
;;
|
;;
|
||||||
"Linux")
|
"Linux")
|
||||||
echo -n "linux "
|
echo -n "linux "
|
||||||
mkdir -p bdk-kotlin/jvm/src/main/resources/linux-x86-64
|
mkdir -p bdk-bdk-kotlin/jvm/src/main/resources/linux-x86-64
|
||||||
cp target/debug/libbdk_ffi.so bdk-kotlin/jvm/src/main/resources/linux-x86-64
|
cp target/debug/libbdk_ffi.so bdk-bdk-kotlin/jvm/src/main/resources/linux-x86-64
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
echo "libs to kotlin sub-project"
|
echo "libs to kotlin sub-project"
|
||||||
}
|
}
|
||||||
|
|
||||||
## bdk-kotlin jar
|
## bdk-bdk-kotlin jar
|
||||||
build_kotlin() {
|
build_kotlin() {
|
||||||
(cd bdk-kotlin && ./gradlew :jvm:build && ./gradlew :jvm:publishToMavenLocal)
|
(cd bdk-bdk-kotlin && ./gradlew :jvm:build && ./gradlew :jvm:publishToMavenLocal)
|
||||||
}
|
}
|
||||||
|
|
||||||
## rust android
|
## rust android
|
||||||
@ -69,27 +69,27 @@ build_android() {
|
|||||||
# IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on
|
# IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on
|
||||||
BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}"
|
BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}"
|
||||||
|
|
||||||
mkdir -p bdk-kotlin/android/src/main/jniLibs/ bdk-kotlin/android/src/main/jniLibs/arm64-v8a bdk-kotlin/android/src/main/jniLibs/x86_64 bdk-kotlin/android/src/main/jniLibs/armeabi-v7a bdk-kotlin/android/src/main/jniLibs/x86
|
mkdir -p bdk-bdk-kotlin/android/src/main/jniLibs/ bdk-bdk-kotlin/android/src/main/jniLibs/arm64-v8a bdk-bdk-kotlin/android/src/main/jniLibs/x86_64 bdk-bdk-kotlin/android/src/main/jniLibs/armeabi-v7a bdk-bdk-kotlin/android/src/main/jniLibs/x86
|
||||||
|
|
||||||
if echo $BUILD_TARGETS | grep "aarch64"; then
|
if echo $BUILD_TARGETS | grep "aarch64"; then
|
||||||
CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android
|
CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android
|
||||||
cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/arm64-v8a
|
cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-bdk-kotlin/android/src/main/jniLibs/arm64-v8a
|
||||||
fi
|
fi
|
||||||
if echo $BUILD_TARGETS | grep "x86_64"; then
|
if echo $BUILD_TARGETS | grep "x86_64"; then
|
||||||
CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android
|
CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android
|
||||||
cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86_64
|
cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-bdk-kotlin/android/src/main/jniLibs/x86_64
|
||||||
fi
|
fi
|
||||||
if echo $BUILD_TARGETS | grep "armv7"; then
|
if echo $BUILD_TARGETS | grep "armv7"; then
|
||||||
CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi
|
CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi
|
||||||
cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/armeabi-v7a
|
cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-bdk-kotlin/android/src/main/jniLibs/armeabi-v7a
|
||||||
fi
|
fi
|
||||||
if echo $BUILD_TARGETS | grep "i686"; then
|
if echo $BUILD_TARGETS | grep "i686"; then
|
||||||
CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android
|
CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android
|
||||||
cp target/i686-linux-android/debug/libbdk_ffi.so bdk-kotlin/android/src/main/jniLibs/x86
|
cp target/i686-linux-android/debug/libbdk_ffi.so bdk-bdk-kotlin/android/src/main/jniLibs/x86
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# bdk-kotlin aar
|
# bdk-bdk-kotlin aar
|
||||||
(cd bdk-kotlin && ./gradlew :android:build && ./gradlew :android:publishToMavenLocal)
|
(cd bdk-bdk-kotlin && ./gradlew :android:build && ./gradlew :android:publishToMavenLocal)
|
||||||
}
|
}
|
||||||
|
|
||||||
OS=$(uname)
|
OS=$(uname)
|
||||||
|
315
cc/bdk_ffi.h
315
cc/bdk_ffi.h
@ -1,315 +0,0 @@
|
|||||||
/*! \file */
|
|
||||||
/*******************************************
|
|
||||||
* *
|
|
||||||
* File auto-generated by `::safer_ffi`. *
|
|
||||||
* *
|
|
||||||
* Do not manually edit this file. *
|
|
||||||
* *
|
|
||||||
*******************************************/
|
|
||||||
|
|
||||||
#ifndef __RUST_BDK_FFI__
|
|
||||||
#define __RUST_BDK_FFI__
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct DatabaseConfig DatabaseConfig_t;
|
|
||||||
|
|
||||||
DatabaseConfig_t * new_memory_config (void);
|
|
||||||
|
|
||||||
DatabaseConfig_t * new_sled_config (
|
|
||||||
char const * path,
|
|
||||||
char const * tree_name);
|
|
||||||
|
|
||||||
void free_database_config (
|
|
||||||
DatabaseConfig_t * database_config);
|
|
||||||
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/** \remark Has the same ABI as `uint16_t` **/
|
|
||||||
#ifdef DOXYGEN
|
|
||||||
typedef enum FfiError
|
|
||||||
#else
|
|
||||||
typedef uint16_t FfiError_t; enum
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_NONE,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_INVALID_U32_BYTES,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_GENERIC,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_SCRIPT_DOESNT_HAVE_ADDRESS_FORM,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_NO_RECIPIENTS,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_NO_UTXOS_SELECTED,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_OUTPUT_BELOW_DUST_LIMIT,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_INSUFFICIENT_FUNDS,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_BN_B_TOTAL_TRIES_EXCEEDED,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_BN_B_NO_EXACT_MATCH,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_UNKNOWN_UTXO,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_TRANSACTION_NOT_FOUND,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_TRANSACTION_CONFIRMED,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_IRREPLACEABLE_TRANSACTION,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_FEE_RATE_TOO_LOW,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_FEE_TOO_LOW,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_FEE_RATE_UNAVAILABLE,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_MISSING_KEY_ORIGIN,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_KEY,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_CHECKSUM_MISMATCH,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_SPENDING_POLICY_REQUIRED,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_INVALID_POLICY_PATH_ERROR,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_SIGNER,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_INVALID_NETWORK,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_INVALID_PROGRESS_VALUE,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_PROGRESS_UPDATE_ERROR,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_INVALID_OUTPOINT,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_DESCRIPTOR,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_ADDRESS_VALIDATOR,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_ENCODE,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_MINISCRIPT,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_BIP32,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_SECP256K1,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_JSON,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_HEX,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_PSBT,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_PSBT_PARSE,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_ELECTRUM,
|
|
||||||
/** . */
|
|
||||||
FFI_ERROR_SLED,
|
|
||||||
}
|
|
||||||
#ifdef DOXYGEN
|
|
||||||
FfiError_t
|
|
||||||
#endif
|
|
||||||
;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
char * ok;
|
|
||||||
|
|
||||||
FfiError_t err;
|
|
||||||
|
|
||||||
} FfiResult_char_ptr_t;
|
|
||||||
|
|
||||||
void free_string_result (
|
|
||||||
FfiResult_char_ptr_t string_result);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
FfiError_t err;
|
|
||||||
|
|
||||||
} FfiResultVoid_t;
|
|
||||||
|
|
||||||
void free_void_result (
|
|
||||||
FfiResultVoid_t void_result);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
uint64_t ok;
|
|
||||||
|
|
||||||
FfiError_t err;
|
|
||||||
|
|
||||||
} FfiResult_uint64_t;
|
|
||||||
|
|
||||||
void free_uint64_result (
|
|
||||||
FfiResult_uint64_t void_result);
|
|
||||||
|
|
||||||
/** \brief
|
|
||||||
* Free a Rust-allocated string
|
|
||||||
*/
|
|
||||||
void free_string (
|
|
||||||
char * string);
|
|
||||||
|
|
||||||
typedef struct BlockchainConfig BlockchainConfig_t;
|
|
||||||
|
|
||||||
typedef struct OpaqueWallet OpaqueWallet_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
OpaqueWallet_t * ok;
|
|
||||||
|
|
||||||
FfiError_t err;
|
|
||||||
|
|
||||||
} FfiResult_OpaqueWallet_ptr_t;
|
|
||||||
|
|
||||||
FfiResult_OpaqueWallet_ptr_t new_wallet_result (
|
|
||||||
char const * descriptor,
|
|
||||||
char const * change_descriptor,
|
|
||||||
char const * network,
|
|
||||||
BlockchainConfig_t const * blockchain_config,
|
|
||||||
DatabaseConfig_t const * database_config);
|
|
||||||
|
|
||||||
void free_wallet_result (
|
|
||||||
FfiResult_OpaqueWallet_ptr_t wallet_result);
|
|
||||||
|
|
||||||
FfiResultVoid_t sync_wallet (
|
|
||||||
OpaqueWallet_t const * opaque_wallet);
|
|
||||||
|
|
||||||
FfiResult_char_ptr_t new_address (
|
|
||||||
OpaqueWallet_t const * opaque_wallet);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
char * txid;
|
|
||||||
|
|
||||||
uint32_t vout;
|
|
||||||
|
|
||||||
} OutPoint_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
uint64_t value;
|
|
||||||
|
|
||||||
char * script_pubkey;
|
|
||||||
|
|
||||||
} TxOut_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
OutPoint_t outpoint;
|
|
||||||
|
|
||||||
TxOut_t txout;
|
|
||||||
|
|
||||||
uint16_t keychain;
|
|
||||||
|
|
||||||
} LocalUtxo_t;
|
|
||||||
|
|
||||||
/** \brief
|
|
||||||
* Same as [`Vec<T>`][`rust::Vec`], but with guaranteed `#[repr(C)]` layout
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
LocalUtxo_t * ptr;
|
|
||||||
|
|
||||||
size_t len;
|
|
||||||
|
|
||||||
size_t cap;
|
|
||||||
|
|
||||||
} Vec_LocalUtxo_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
Vec_LocalUtxo_t ok;
|
|
||||||
|
|
||||||
FfiError_t err;
|
|
||||||
|
|
||||||
} FfiResult_Vec_LocalUtxo_t;
|
|
||||||
|
|
||||||
FfiResult_Vec_LocalUtxo_t list_unspent (
|
|
||||||
OpaqueWallet_t const * opaque_wallet);
|
|
||||||
|
|
||||||
void free_veclocalutxo_result (
|
|
||||||
FfiResult_Vec_LocalUtxo_t unspent_result);
|
|
||||||
|
|
||||||
FfiResult_uint64_t balance (
|
|
||||||
OpaqueWallet_t const * opaque_wallet);
|
|
||||||
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
uint32_t height;
|
|
||||||
|
|
||||||
uint64_t timestamp;
|
|
||||||
|
|
||||||
} ConfirmationTime_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
char * txid;
|
|
||||||
|
|
||||||
uint64_t received;
|
|
||||||
|
|
||||||
uint64_t sent;
|
|
||||||
|
|
||||||
int64_t fee;
|
|
||||||
|
|
||||||
bool is_confirmed;
|
|
||||||
|
|
||||||
ConfirmationTime_t confirmation_time;
|
|
||||||
|
|
||||||
bool verified;
|
|
||||||
|
|
||||||
} TransactionDetails_t;
|
|
||||||
|
|
||||||
/** \brief
|
|
||||||
* Same as [`Vec<T>`][`rust::Vec`], but with guaranteed `#[repr(C)]` layout
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
TransactionDetails_t * ptr;
|
|
||||||
|
|
||||||
size_t len;
|
|
||||||
|
|
||||||
size_t cap;
|
|
||||||
|
|
||||||
} Vec_TransactionDetails_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
Vec_TransactionDetails_t ok;
|
|
||||||
|
|
||||||
FfiError_t err;
|
|
||||||
|
|
||||||
} FfiResult_Vec_TransactionDetails_t;
|
|
||||||
|
|
||||||
FfiResult_Vec_TransactionDetails_t list_transactions (
|
|
||||||
OpaqueWallet_t const * opaque_wallet);
|
|
||||||
|
|
||||||
void free_vectxdetails_result (
|
|
||||||
FfiResult_Vec_TransactionDetails_t txdetails_result);
|
|
||||||
|
|
||||||
BlockchainConfig_t * new_electrum_config (
|
|
||||||
char const * url,
|
|
||||||
char const * socks5,
|
|
||||||
int16_t retry,
|
|
||||||
int16_t timeout,
|
|
||||||
size_t stop_gap);
|
|
||||||
|
|
||||||
void free_blockchain_config (
|
|
||||||
BlockchainConfig_t * blockchain_config);
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
} /* extern "C" */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* __RUST_BDK_FFI__ */
|
|
@ -1,199 +0,0 @@
|
|||||||
#include <assert.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "bdk_ffi.h"
|
|
||||||
|
|
||||||
int main (int argc, char const * const argv[])
|
|
||||||
{
|
|
||||||
|
|
||||||
// shared consts
|
|
||||||
char const *desc = "wpkh([bf988dd3/84'/1'/0']tpubDD7bHVspyCSvvU8qEycydF664NAX6EAPjJ77j9E614GU2zVdXgnZZo6JJjKbDT6fUn8owMN6TCP9rZMznsNEhJbpkEwp6fAyyoSqy3DH2Qj/0/*)";
|
|
||||||
char const *change = "wpkh([bf988dd3/84'/1'/0']tpubDD7bHVspyCSvvU8qEycydF664NAX6EAPjJ77j9E614GU2zVdXgnZZo6JJjKbDT6fUn8owMN6TCP9rZMznsNEhJbpkEwp6fAyyoSqy3DH2Qj/1/*)";
|
|
||||||
char const *net = "testnet";
|
|
||||||
char const *blocks = "ssl://electrum.blockstream.info:60002";
|
|
||||||
|
|
||||||
// test new wallet error
|
|
||||||
{
|
|
||||||
BlockchainConfig_t *bc_config = new_electrum_config(blocks, NULL, 5, 30, 100);
|
|
||||||
//DatabaseConfig_t *db_config = new_sled_config("/home/steve/.bdk", "test_wallet");
|
|
||||||
DatabaseConfig_t *db_config = new_memory_config();
|
|
||||||
|
|
||||||
// new wallet with bad descriptor
|
|
||||||
FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result("bad","bad",net,bc_config,db_config);
|
|
||||||
assert(wallet_result.err == FFI_ERROR_DESCRIPTOR);
|
|
||||||
assert(wallet_result.ok == NULL);
|
|
||||||
|
|
||||||
free_blockchain_config(bc_config);
|
|
||||||
free_database_config(db_config);
|
|
||||||
|
|
||||||
free_wallet_result(wallet_result);
|
|
||||||
}
|
|
||||||
|
|
||||||
// test new wallet
|
|
||||||
{
|
|
||||||
BlockchainConfig_t *bc_config = new_electrum_config(blocks, NULL, 5, 30, 100);
|
|
||||||
DatabaseConfig_t *db_config = new_memory_config();
|
|
||||||
|
|
||||||
// new wallet
|
|
||||||
FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,net,bc_config,db_config);
|
|
||||||
// printf("wallet_result.err = %d\n", wallet_result.err));
|
|
||||||
assert(wallet_result.err == FFI_ERROR_NONE);
|
|
||||||
assert(wallet_result.ok != NULL);
|
|
||||||
|
|
||||||
free_blockchain_config(bc_config);
|
|
||||||
free_database_config(db_config);
|
|
||||||
|
|
||||||
OpaqueWallet_t *wallet = wallet_result.ok;
|
|
||||||
|
|
||||||
// sync wallet
|
|
||||||
FfiResultVoid_t sync_result = sync_wallet(wallet);
|
|
||||||
assert(sync_result.err == FFI_ERROR_NONE);
|
|
||||||
free_void_result(sync_result);
|
|
||||||
|
|
||||||
// new address
|
|
||||||
FfiResult_char_ptr_t address1_result = new_address(wallet);
|
|
||||||
assert(address1_result.ok != NULL);
|
|
||||||
assert(address1_result.err == FFI_ERROR_NONE);
|
|
||||||
//printf("address1 = %s\n", address1_result.ok);
|
|
||||||
assert( 0 == strcmp(address1_result.ok,"tb1qh4ajvhz9nd76tqddnl99l89hx4dat33hrjauzw"));
|
|
||||||
free_string_result(address1_result);
|
|
||||||
|
|
||||||
FfiResult_char_ptr_t address2_result = new_address(wallet);
|
|
||||||
assert(address2_result.ok != NULL);
|
|
||||||
assert(address2_result.err == FFI_ERROR_NONE);
|
|
||||||
//printf("address2 = %s\n", address2_result.ok);
|
|
||||||
assert( 0 == strcmp(address2_result.ok,"tb1qr7pu0pech43hcjrc4pzxcen0qkslj7xk7s5w3m"));
|
|
||||||
free_string_result(address2_result);
|
|
||||||
|
|
||||||
// free_wallet
|
|
||||||
free_wallet_result(wallet_result);
|
|
||||||
|
|
||||||
// verify free_wallet after free_wallet fails (core dumped)
|
|
||||||
//// free_wallet_result(wallet_result);
|
|
||||||
|
|
||||||
// verify sync_wallet after free_wallet fails (core dumped)
|
|
||||||
//// FfiResultVoid_t sync_result2 = sync_wallet(wallet);
|
|
||||||
}
|
|
||||||
|
|
||||||
// test get unspent utxos
|
|
||||||
{
|
|
||||||
BlockchainConfig_t *bc_config = new_electrum_config(blocks, NULL, 5, 30, 100);
|
|
||||||
DatabaseConfig_t *db_config = new_memory_config();
|
|
||||||
|
|
||||||
// new wallet
|
|
||||||
FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,net,bc_config,db_config);
|
|
||||||
assert(wallet_result.err == FFI_ERROR_NONE);
|
|
||||||
assert(wallet_result.ok != NULL);
|
|
||||||
|
|
||||||
free_blockchain_config(bc_config);
|
|
||||||
free_database_config(db_config);
|
|
||||||
|
|
||||||
OpaqueWallet_t *wallet = wallet_result.ok;
|
|
||||||
|
|
||||||
// sync wallet
|
|
||||||
FfiResultVoid_t sync_result = sync_wallet(wallet);
|
|
||||||
assert(sync_result.err == FFI_ERROR_NONE);
|
|
||||||
free_void_result(sync_result);
|
|
||||||
|
|
||||||
// list unspent
|
|
||||||
FfiResult_Vec_LocalUtxo_t unspent_result = list_unspent(wallet);
|
|
||||||
assert(unspent_result.ok.len == 1);
|
|
||||||
assert(unspent_result.err == FFI_ERROR_NONE);
|
|
||||||
|
|
||||||
LocalUtxo_t * unspent_ptr = unspent_result.ok.ptr;
|
|
||||||
for (int i = 0; i < unspent_result.ok.len; i++) {
|
|
||||||
// printf("%d: outpoint.txid: %s\n", i, unspent_ptr[i].outpoint.txid);
|
|
||||||
assert(unspent_ptr[i].outpoint.txid != NULL);
|
|
||||||
// printf("%d: outpoint.vout: %d\n", i, unspent_ptr[i].outpoint.vout);
|
|
||||||
assert(unspent_ptr[i].outpoint.vout >= 0);
|
|
||||||
// printf("%d: txout.value: %ld\n", i, unspent_ptr[i].txout.value);
|
|
||||||
assert(unspent_ptr[i].txout.value > 0);
|
|
||||||
// printf("%d: txout.script_pubkey: %s\n", i, unspent_ptr[i].txout.script_pubkey);
|
|
||||||
assert(unspent_ptr[i].txout.script_pubkey != NULL);
|
|
||||||
// printf("%d: keychain: %d\n", i, unspent_ptr[i].keychain);
|
|
||||||
assert(unspent_ptr[i].keychain >= 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
free_veclocalutxo_result(unspent_result);
|
|
||||||
free_wallet_result(wallet_result);
|
|
||||||
}
|
|
||||||
|
|
||||||
// test balance
|
|
||||||
{
|
|
||||||
BlockchainConfig_t *bc_config = new_electrum_config(blocks, NULL, 5, 30, 100);
|
|
||||||
DatabaseConfig_t *db_config = new_memory_config();
|
|
||||||
|
|
||||||
// new wallet
|
|
||||||
FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,net,bc_config,db_config);
|
|
||||||
assert(wallet_result.err == FFI_ERROR_NONE);
|
|
||||||
assert(wallet_result.ok != NULL);
|
|
||||||
|
|
||||||
free_blockchain_config(bc_config);
|
|
||||||
free_database_config(db_config);
|
|
||||||
|
|
||||||
OpaqueWallet_t *wallet = wallet_result.ok;
|
|
||||||
|
|
||||||
// sync wallet
|
|
||||||
FfiResultVoid_t sync_result = sync_wallet(wallet);
|
|
||||||
assert(sync_result.err == FFI_ERROR_NONE);
|
|
||||||
free_void_result(sync_result);
|
|
||||||
|
|
||||||
// get balance
|
|
||||||
FfiResult_uint64_t balance_result = balance(wallet);
|
|
||||||
//printf("balance.err = %d\n", (balance_result.err));
|
|
||||||
assert(balance_result.err == FFI_ERROR_NONE);
|
|
||||||
//printf("balance.ok = %ld\n", balance_result.ok);
|
|
||||||
assert(balance_result.ok > 0);
|
|
||||||
|
|
||||||
// free balance and wallet results
|
|
||||||
free_uint64_result(balance_result);
|
|
||||||
free_wallet_result(wallet_result);
|
|
||||||
}
|
|
||||||
|
|
||||||
// test get transaction details
|
|
||||||
{
|
|
||||||
BlockchainConfig_t *bc_config = new_electrum_config(blocks, NULL, 5, 30, 100);
|
|
||||||
DatabaseConfig_t *db_config = new_memory_config();
|
|
||||||
|
|
||||||
// new wallet
|
|
||||||
FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,net,bc_config,db_config);
|
|
||||||
assert(wallet_result.err == FFI_ERROR_NONE);
|
|
||||||
assert(wallet_result.ok != NULL);
|
|
||||||
|
|
||||||
free_blockchain_config(bc_config);
|
|
||||||
free_database_config(db_config);
|
|
||||||
|
|
||||||
OpaqueWallet_t *wallet = wallet_result.ok;
|
|
||||||
|
|
||||||
// sync wallet
|
|
||||||
FfiResultVoid_t sync_result = sync_wallet(wallet);
|
|
||||||
assert(sync_result.err == FFI_ERROR_NONE);
|
|
||||||
free_void_result(sync_result);
|
|
||||||
|
|
||||||
// list transactions
|
|
||||||
FfiResult_Vec_TransactionDetails_t txdetails_result = list_transactions(wallet);
|
|
||||||
assert(txdetails_result.ok.len > 0);
|
|
||||||
assert(txdetails_result.err == FFI_ERROR_NONE);
|
|
||||||
|
|
||||||
TransactionDetails_t * txdetails_ptr = txdetails_result.ok.ptr;
|
|
||||||
for (int i = 0; i < txdetails_result.ok.len; i++) {
|
|
||||||
//printf("%d: txid: %s\n", i, txdetails_ptr[i].txid);
|
|
||||||
assert(txdetails_ptr[i].txid != NULL);
|
|
||||||
//printf("%d: timestamp: %ld\n", i, txdetails_ptr[i].timestamp);
|
|
||||||
assert(txdetails_ptr[i].is_confirmed);
|
|
||||||
//printf("%d: received: %ld\n", i, txdetails_ptr[i].received);
|
|
||||||
//printf("%d: sent: %ld\n", i, txdetails_ptr[i].sent);
|
|
||||||
assert(txdetails_ptr[i].received > 0 || txdetails_ptr[i].sent > 0);
|
|
||||||
//printf("%d: fees: %ld\n", i, txdetails_ptr[i].fees);
|
|
||||||
assert(txdetails_ptr[i].fee > 0);
|
|
||||||
//printf("%d: height: %d\n", i, txdetails_ptr[i].height);
|
|
||||||
assert(txdetails_ptr[i].confirmation_time.height > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
free_vectxdetails_result(txdetails_result);
|
|
||||||
free_wallet_result(wallet_result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
134
src/error.rs
134
src/error.rs
@ -1,134 +0,0 @@
|
|||||||
use bdk::Error;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum FfiError {
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
None,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
InvalidU32Bytes,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
Generic,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
ScriptDoesntHaveAddressForm,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
NoRecipients,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
NoUtxosSelected,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
OutputBelowDustLimit,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
InsufficientFunds,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
BnBTotalTriesExceeded,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
BnBNoExactMatch,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
UnknownUtxo,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
TransactionNotFound,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
TransactionConfirmed,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
IrreplaceableTransaction,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
FeeRateTooLow,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
FeeTooLow,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
FeeRateUnavailable,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
MissingKeyOrigin,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
Key,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
ChecksumMismatch,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
SpendingPolicyRequired,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
InvalidPolicyPathError,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
Signer,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
InvalidNetwork,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
InvalidProgressValue,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
ProgressUpdateError,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
InvalidOutpoint,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
Descriptor,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
AddressValidator,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
Encode,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
Miniscript,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
Bip32,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
Secp256k1,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
Json,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
Hex,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
Psbt,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
PsbtParse,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
Electrum,
|
|
||||||
// Esplora,
|
|
||||||
// CompactFilters,
|
|
||||||
#[error("data store disconnected")]
|
|
||||||
Sled,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<bdk::Error> for FfiError {
|
|
||||||
fn from(error: bdk::Error) -> Self {
|
|
||||||
match error {
|
|
||||||
Error::InvalidU32Bytes(_) => FfiError::InvalidU32Bytes,
|
|
||||||
Error::Generic(_) => FfiError::Generic,
|
|
||||||
Error::ScriptDoesntHaveAddressForm => FfiError::ScriptDoesntHaveAddressForm,
|
|
||||||
Error::NoRecipients => FfiError::NoRecipients,
|
|
||||||
Error::NoUtxosSelected => FfiError::NoUtxosSelected,
|
|
||||||
Error::OutputBelowDustLimit(_) => FfiError::OutputBelowDustLimit,
|
|
||||||
Error::InsufficientFunds { .. } => FfiError::InsufficientFunds,
|
|
||||||
Error::BnBTotalTriesExceeded => FfiError::BnBTotalTriesExceeded,
|
|
||||||
Error::BnBNoExactMatch => FfiError::BnBNoExactMatch,
|
|
||||||
Error::UnknownUtxo => FfiError::UnknownUtxo,
|
|
||||||
Error::TransactionNotFound => FfiError::TransactionNotFound,
|
|
||||||
Error::TransactionConfirmed => FfiError::TransactionConfirmed,
|
|
||||||
Error::IrreplaceableTransaction => FfiError::IrreplaceableTransaction,
|
|
||||||
Error::FeeRateTooLow { .. } => FfiError::FeeRateTooLow,
|
|
||||||
Error::FeeTooLow { .. } => FfiError::FeeTooLow,
|
|
||||||
Error::FeeRateUnavailable => FfiError::FeeRateUnavailable,
|
|
||||||
Error::MissingKeyOrigin(_) => FfiError::MissingKeyOrigin,
|
|
||||||
Error::Key(_) => FfiError::Key,
|
|
||||||
Error::ChecksumMismatch => FfiError::ChecksumMismatch,
|
|
||||||
Error::SpendingPolicyRequired(_) => FfiError::SpendingPolicyRequired,
|
|
||||||
Error::InvalidPolicyPathError(_) => FfiError::InvalidPolicyPathError,
|
|
||||||
Error::Signer(_) => FfiError::Signer,
|
|
||||||
Error::InvalidNetwork { .. } => FfiError::InvalidNetwork,
|
|
||||||
Error::InvalidProgressValue(_) => FfiError::InvalidProgressValue,
|
|
||||||
Error::ProgressUpdateError => FfiError::ProgressUpdateError,
|
|
||||||
Error::InvalidOutpoint(_) => FfiError::InvalidOutpoint,
|
|
||||||
Error::Descriptor(_) => FfiError::Descriptor,
|
|
||||||
Error::AddressValidator(_) => FfiError::AddressValidator,
|
|
||||||
Error::Encode(_) => FfiError::Encode,
|
|
||||||
Error::Miniscript(_) => FfiError::Miniscript,
|
|
||||||
Error::Bip32(_) => FfiError::Bip32,
|
|
||||||
Error::Secp256k1(_) => FfiError::Secp256k1,
|
|
||||||
Error::Json(_) => FfiError::Json,
|
|
||||||
Error::Hex(_) => FfiError::Hex,
|
|
||||||
Error::Psbt(_) => FfiError::Psbt,
|
|
||||||
Error::PsbtParse(_) => FfiError::PsbtParse,
|
|
||||||
Error::Electrum(_) => FfiError::Electrum,
|
|
||||||
// Error::Esplora(_) => JniError::Esplora,
|
|
||||||
// Error::CompactFilters(_) => JniError::CompactFilters,
|
|
||||||
// Error::Rpc(_) => JniError::Rpc,
|
|
||||||
Error::Sled(_) => FfiError::Sled,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
40
src/types.rs
40
src/types.rs
@ -1,40 +0,0 @@
|
|||||||
use crate::error::FfiError;
|
|
||||||
use ::safer_ffi::prelude::*;
|
|
||||||
use safer_ffi::char_p::char_p_boxed;
|
|
||||||
|
|
||||||
#[derive_ReprC]
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct FfiResult<T> {
|
|
||||||
pub ok: T,
|
|
||||||
pub err: FfiError,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive_ReprC]
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct FfiResultVoid {
|
|
||||||
pub err: FfiError,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn free_string_result(string_result: FfiResult<char_p_boxed>) {
|
|
||||||
drop(string_result)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn free_void_result(void_result: FfiResultVoid) {
|
|
||||||
drop(void_result)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn free_uint64_result(void_result: FfiResult<u64>) {
|
|
||||||
drop(void_result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO do we need this? remove?
|
|
||||||
/// Free a Rust-allocated string
|
|
||||||
#[ffi_export]
|
|
||||||
fn free_string(string: Option<char_p_boxed>) {
|
|
||||||
drop(string)
|
|
||||||
}
|
|
@ -1,602 +0,0 @@
|
|||||||
// This file was autogenerated by some hot garbage in the `uniffi` crate.
|
|
||||||
// Trust me, you don't want to mess with it!
|
|
||||||
|
|
||||||
@file:Suppress("NAME_SHADOWING")
|
|
||||||
|
|
||||||
package uniffi.bdk;
|
|
||||||
|
|
||||||
// Common helper code.
|
|
||||||
//
|
|
||||||
// Ideally this would live in a separate .kt file where it can be unittested etc
|
|
||||||
// in isolation, and perhaps even published as a re-useable package.
|
|
||||||
//
|
|
||||||
// However, it's important that the detils of how this helper code works (e.g. the
|
|
||||||
// way that different builtin types are passed across the FFI) exactly match what's
|
|
||||||
// expected by the Rust code on the other side of the interface. In practice right
|
|
||||||
// now that means coming from the exact some version of `uniffi` that was used to
|
|
||||||
// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin
|
|
||||||
// helpers directly inline like we're doing here.
|
|
||||||
|
|
||||||
import com.sun.jna.Library
|
|
||||||
import com.sun.jna.Native
|
|
||||||
import com.sun.jna.Pointer
|
|
||||||
import com.sun.jna.Structure
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
|
||||||
import kotlin.concurrent.withLock
|
|
||||||
|
|
||||||
// This is a helper for safely working with byte buffers returned from the Rust code.
|
|
||||||
// A rust-owned buffer is represented by its capacity, its current length, and a
|
|
||||||
// pointer to the underlying data.
|
|
||||||
|
|
||||||
@Structure.FieldOrder("capacity", "len", "data")
|
|
||||||
open class RustBuffer : Structure() {
|
|
||||||
@JvmField var capacity: Int = 0
|
|
||||||
@JvmField var len: Int = 0
|
|
||||||
@JvmField var data: Pointer? = null
|
|
||||||
|
|
||||||
class ByValue : RustBuffer(), Structure.ByValue
|
|
||||||
class ByReference : RustBuffer(), Structure.ByReference
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
internal fun alloc(size: Int = 0) = rustCall() { status ->
|
|
||||||
_UniFFILib.INSTANCE.ffi_bdk_ed55_rustbuffer_alloc(size, status)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun free(buf: RustBuffer.ByValue) = rustCall() { status ->
|
|
||||||
_UniFFILib.INSTANCE.ffi_bdk_ed55_rustbuffer_free(buf, status)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status ->
|
|
||||||
_UniFFILib.INSTANCE.ffi_bdk_ed55_rustbuffer_reserve(buf, additional, status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("TooGenericExceptionThrown")
|
|
||||||
fun asByteBuffer() =
|
|
||||||
this.data?.getByteBuffer(0, this.len.toLong())?.also {
|
|
||||||
it.order(ByteOrder.BIG_ENDIAN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a helper for safely passing byte references into the rust code.
|
|
||||||
// It's not actually used at the moment, because there aren't many things that you
|
|
||||||
// can take a direct pointer to in the JVM, and if we're going to copy something
|
|
||||||
// then we might as well copy it into a `RustBuffer`. But it's here for API
|
|
||||||
// completeness.
|
|
||||||
|
|
||||||
@Structure.FieldOrder("len", "data")
|
|
||||||
open class ForeignBytes : Structure() {
|
|
||||||
@JvmField var len: Int = 0
|
|
||||||
@JvmField var data: Pointer? = null
|
|
||||||
|
|
||||||
class ByValue : ForeignBytes(), Structure.ByValue
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// A helper for structured writing of data into a `RustBuffer`.
|
|
||||||
// This is very similar to `java.nio.ByteBuffer` but it knows how to grow
|
|
||||||
// the underlying `RustBuffer` on demand.
|
|
||||||
//
|
|
||||||
// TODO: we should benchmark writing things into a `RustBuffer` versus building
|
|
||||||
// up a bytearray and then copying it across.
|
|
||||||
|
|
||||||
class RustBufferBuilder() {
|
|
||||||
var rbuf = RustBuffer.ByValue()
|
|
||||||
var bbuf: ByteBuffer? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
val rbuf = RustBuffer.alloc(16) // Totally arbitrary initial size
|
|
||||||
rbuf.writeField("len", 0)
|
|
||||||
this.setRustBuffer(rbuf)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun setRustBuffer(rbuf: RustBuffer.ByValue) {
|
|
||||||
this.rbuf = rbuf
|
|
||||||
this.bbuf = this.rbuf.data?.getByteBuffer(0, this.rbuf.capacity.toLong())?.also {
|
|
||||||
it.order(ByteOrder.BIG_ENDIAN)
|
|
||||||
it.position(rbuf.len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun finalize() : RustBuffer.ByValue {
|
|
||||||
val rbuf = this.rbuf
|
|
||||||
// Ensure that the JVM-level field is written through to native memory
|
|
||||||
// before turning the buffer, in case its recipient uses it in a context
|
|
||||||
// JNA doesn't apply its automatic synchronization logic.
|
|
||||||
rbuf.writeField("len", this.bbuf!!.position())
|
|
||||||
this.setRustBuffer(RustBuffer.ByValue())
|
|
||||||
return rbuf
|
|
||||||
}
|
|
||||||
|
|
||||||
fun discard() {
|
|
||||||
val rbuf = this.finalize()
|
|
||||||
RustBuffer.free(rbuf)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun reserve(size: Int, write: (ByteBuffer) -> Unit) {
|
|
||||||
// TODO: this will perform two checks to ensure we're not overflowing the buffer:
|
|
||||||
// one here where we check if it needs to grow, and another when we call a write
|
|
||||||
// method on the ByteBuffer. It might be cheaper to use exception-driven control-flow
|
|
||||||
// here, trying the write and growing if it throws a `BufferOverflowException`.
|
|
||||||
// Benchmarking needed.
|
|
||||||
if (this.bbuf!!.position() + size > this.rbuf.capacity) {
|
|
||||||
rbuf.writeField("len", this.bbuf!!.position())
|
|
||||||
this.setRustBuffer(RustBuffer.reserve(this.rbuf, size))
|
|
||||||
}
|
|
||||||
write(this.bbuf!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun putByte(v: Byte) {
|
|
||||||
this.reserve(1) { bbuf ->
|
|
||||||
bbuf.put(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun putShort(v: Short) {
|
|
||||||
this.reserve(2) { bbuf ->
|
|
||||||
bbuf.putShort(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun putInt(v: Int) {
|
|
||||||
this.reserve(4) { bbuf ->
|
|
||||||
bbuf.putInt(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun putLong(v: Long) {
|
|
||||||
this.reserve(8) { bbuf ->
|
|
||||||
bbuf.putLong(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun putFloat(v: Float) {
|
|
||||||
this.reserve(4) { bbuf ->
|
|
||||||
bbuf.putFloat(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun putDouble(v: Double) {
|
|
||||||
this.reserve(8) { bbuf ->
|
|
||||||
bbuf.putDouble(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun put(v: ByteArray) {
|
|
||||||
this.reserve(v.size) { bbuf ->
|
|
||||||
bbuf.put(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers for reading primitive data types from a bytebuffer.
|
|
||||||
|
|
||||||
internal fun<T> liftFromRustBuffer(rbuf: RustBuffer.ByValue, readItem: (ByteBuffer) -> T): T {
|
|
||||||
val buf = rbuf.asByteBuffer()!!
|
|
||||||
try {
|
|
||||||
val item = readItem(buf)
|
|
||||||
if (buf.hasRemaining()) {
|
|
||||||
throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!")
|
|
||||||
}
|
|
||||||
return item
|
|
||||||
} finally {
|
|
||||||
RustBuffer.free(rbuf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun<T> lowerIntoRustBuffer(v: T, writeItem: (T, RustBufferBuilder) -> Unit): RustBuffer.ByValue {
|
|
||||||
// TODO: maybe we can calculate some sort of initial size hint?
|
|
||||||
val buf = RustBufferBuilder()
|
|
||||||
try {
|
|
||||||
writeItem(v, buf)
|
|
||||||
return buf.finalize()
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
buf.discard()
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For every type used in the interface, we provide helper methods for conveniently
|
|
||||||
// lifting and lowering that type from C-compatible data, and for reading and writing
|
|
||||||
// values of that type in a buffer.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
internal fun String.Companion.lift(rbuf: RustBuffer.ByValue): String {
|
|
||||||
try {
|
|
||||||
val byteArr = ByteArray(rbuf.len)
|
|
||||||
rbuf.asByteBuffer()!!.get(byteArr)
|
|
||||||
return byteArr.toString(Charsets.UTF_8)
|
|
||||||
} finally {
|
|
||||||
RustBuffer.free(rbuf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun String.Companion.read(buf: ByteBuffer): String {
|
|
||||||
val len = buf.getInt()
|
|
||||||
val byteArr = ByteArray(len)
|
|
||||||
buf.get(byteArr)
|
|
||||||
return byteArr.toString(Charsets.UTF_8)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun String.lower(): RustBuffer.ByValue {
|
|
||||||
val byteArr = this.toByteArray(Charsets.UTF_8)
|
|
||||||
// Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us
|
|
||||||
// to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`.
|
|
||||||
val rbuf = RustBuffer.alloc(byteArr.size)
|
|
||||||
rbuf.asByteBuffer()!!.put(byteArr)
|
|
||||||
return rbuf
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun String.write(buf: RustBufferBuilder) {
|
|
||||||
val byteArr = this.toByteArray(Charsets.UTF_8)
|
|
||||||
buf.putInt(byteArr.size)
|
|
||||||
buf.put(byteArr)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun findLibraryName(componentName: String): String {
|
|
||||||
val libOverride = System.getProperty("uniffi.component.${componentName}.libraryOverride")
|
|
||||||
if (libOverride != null) {
|
|
||||||
return libOverride
|
|
||||||
}
|
|
||||||
return "uniffi_bdk"
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified Lib : Library> loadIndirect(
|
|
||||||
componentName: String
|
|
||||||
): Lib {
|
|
||||||
return Native.load<Lib>(findLibraryName(componentName), Lib::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A JNA Library to expose the extern-C FFI definitions.
|
|
||||||
// This is an implementation detail which will be called internally by the public API.
|
|
||||||
|
|
||||||
internal interface _UniFFILib : Library {
|
|
||||||
companion object {
|
|
||||||
internal val INSTANCE: _UniFFILib by lazy {
|
|
||||||
loadIndirect<_UniFFILib>(componentName = "bdk")
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ffi_bdk_ed55_OfflineWallet_object_free(ptr: Pointer,
|
|
||||||
uniffi_out_err: RustCallStatus
|
|
||||||
): Unit
|
|
||||||
|
|
||||||
fun bdk_ed55_OfflineWallet_new(descriptor: RustBuffer.ByValue,
|
|
||||||
uniffi_out_err: RustCallStatus
|
|
||||||
): Pointer
|
|
||||||
|
|
||||||
fun ffi_bdk_ed55_rustbuffer_alloc(size: Int,
|
|
||||||
uniffi_out_err: RustCallStatus
|
|
||||||
): RustBuffer.ByValue
|
|
||||||
|
|
||||||
fun ffi_bdk_ed55_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue,
|
|
||||||
uniffi_out_err: RustCallStatus
|
|
||||||
): RustBuffer.ByValue
|
|
||||||
|
|
||||||
fun ffi_bdk_ed55_rustbuffer_free(buf: RustBuffer.ByValue,
|
|
||||||
uniffi_out_err: RustCallStatus
|
|
||||||
): Unit
|
|
||||||
|
|
||||||
fun ffi_bdk_ed55_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int,
|
|
||||||
uniffi_out_err: RustCallStatus
|
|
||||||
): RustBuffer.ByValue
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// A handful of classes and functions to support the generated data structures.
|
|
||||||
// This would be a good candidate for isolating in its own ffi-support lib.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Interface implemented by anything that can contain an object reference.
|
|
||||||
//
|
|
||||||
// Such types expose a `destroy()` method that must be called to cleanly
|
|
||||||
// dispose of the contained objects. Failure to call this method may result
|
|
||||||
// in memory leaks.
|
|
||||||
//
|
|
||||||
// The easiest way to ensure this method is called is to use the `.use`
|
|
||||||
// helper method to execute a block and destroy the object at the end.
|
|
||||||
interface Disposable {
|
|
||||||
fun destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T : Disposable?, R> T.use(block: (T) -> R) =
|
|
||||||
try {
|
|
||||||
block(this)
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
// N.B. our implementation is on the nullable type `Disposable?`.
|
|
||||||
this?.destroy()
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
// swallow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The base class for all UniFFI Object types.
|
|
||||||
//
|
|
||||||
// This class provides core operations for working with the Rust `Arc<T>` pointer to
|
|
||||||
// the live Rust struct on the other side of the FFI.
|
|
||||||
//
|
|
||||||
// There's some subtlety here, because we have to be careful not to operate on a Rust
|
|
||||||
// struct after it has been dropped, and because we must expose a public API for freeing
|
|
||||||
// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
|
|
||||||
//
|
|
||||||
// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct.
|
|
||||||
// Method calls need to read this pointer from the object's state and pass it in to
|
|
||||||
// the Rust FFI.
|
|
||||||
//
|
|
||||||
// * When an `FFIObject` is no longer needed, its pointer should be passed to a
|
|
||||||
// special destructor function provided by the Rust FFI, which will drop the
|
|
||||||
// underlying Rust struct.
|
|
||||||
//
|
|
||||||
// * Given an `FFIObject` instance, calling code is expected to call the special
|
|
||||||
// `destroy` method in order to free it after use, either by calling it explicitly
|
|
||||||
// or by using a higher-level helper like the `use` method. Failing to do so will
|
|
||||||
// leak the underlying Rust struct.
|
|
||||||
//
|
|
||||||
// * We can't assume that calling code will do the right thing, and must be prepared
|
|
||||||
// to handle Kotlin method calls executing concurrently with or even after a call to
|
|
||||||
// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
|
|
||||||
//
|
|
||||||
// * We must never allow Rust code to operate on the underlying Rust struct after
|
|
||||||
// the destructor has been called, and must never call the destructor more than once.
|
|
||||||
// Doing so may trigger memory unsafety.
|
|
||||||
//
|
|
||||||
// If we try to implement this with mutual exclusion on access to the pointer, there is the
|
|
||||||
// possibility of a race between a method call and a concurrent call to `destroy`:
|
|
||||||
//
|
|
||||||
// * Thread A starts a method call, reads the value of the pointer, but is interrupted
|
|
||||||
// before it can pass the pointer over the FFI to Rust.
|
|
||||||
// * Thread B calls `destroy` and frees the underlying Rust struct.
|
|
||||||
// * Thread A resumes, passing the already-read pointer value to Rust and triggering
|
|
||||||
// a use-after-free.
|
|
||||||
//
|
|
||||||
// One possible solution would be to use a `ReadWriteLock`, with each method call taking
|
|
||||||
// a read lock (and thus allowed to run concurrently) and the special `destroy` method
|
|
||||||
// taking a write lock (and thus blocking on live method calls). However, we aim not to
|
|
||||||
// generate methods with any hidden blocking semantics, and a `destroy` method that might
|
|
||||||
// block if called incorrectly seems to meet that bar.
|
|
||||||
//
|
|
||||||
// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track
|
|
||||||
// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
|
|
||||||
// has been called. These are updated according to the following rules:
|
|
||||||
//
|
|
||||||
// * The initial value of the counter is 1, indicating a live object with no in-flight calls.
|
|
||||||
// The initial value for the flag is false.
|
|
||||||
//
|
|
||||||
// * At the start of each method call, we atomically check the counter.
|
|
||||||
// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
|
|
||||||
// If it is nonzero them we atomically increment it by 1 and proceed with the method call.
|
|
||||||
//
|
|
||||||
// * At the end of each method call, we atomically decrement and check the counter.
|
|
||||||
// If it has reached zero then we destroy the underlying Rust struct.
|
|
||||||
//
|
|
||||||
// * When `destroy` is called, we atomically flip the flag from false to true.
|
|
||||||
// If the flag was already true we silently fail.
|
|
||||||
// Otherwise we atomically decrement and check the counter.
|
|
||||||
// If it has reached zero then we destroy the underlying Rust struct.
|
|
||||||
//
|
|
||||||
// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
|
|
||||||
// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
|
|
||||||
//
|
|
||||||
// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
|
|
||||||
// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
|
|
||||||
// of the underlying Rust code.
|
|
||||||
//
|
|
||||||
// In the future we may be able to replace some of this with automatic finalization logic, such as using
|
|
||||||
// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is
|
|
||||||
// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also
|
|
||||||
// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1],
|
|
||||||
// so there would still be some complexity here).
|
|
||||||
//
|
|
||||||
// Sigh...all of this for want of a robust finalization mechanism.
|
|
||||||
//
|
|
||||||
// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
|
|
||||||
//
|
|
||||||
abstract class FFIObject(
|
|
||||||
protected val pointer: Pointer
|
|
||||||
): Disposable, AutoCloseable {
|
|
||||||
|
|
||||||
val wasDestroyed = AtomicBoolean(false)
|
|
||||||
val callCounter = AtomicLong(1)
|
|
||||||
|
|
||||||
open protected fun freeRustArcPtr() {
|
|
||||||
// To be overridden in subclasses.
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun destroy() {
|
|
||||||
// Only allow a single call to this method.
|
|
||||||
// TODO: maybe we should log a warning if called more than once?
|
|
||||||
if (this.wasDestroyed.compareAndSet(false, true)) {
|
|
||||||
// This decrement always matches the initial count of 1 given at creation time.
|
|
||||||
if (this.callCounter.decrementAndGet() == 0L) {
|
|
||||||
this.freeRustArcPtr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
override fun close() {
|
|
||||||
this.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R {
|
|
||||||
// Check and increment the call counter, to keep the object alive.
|
|
||||||
// This needs a compare-and-set retry loop in case of concurrent updates.
|
|
||||||
do {
|
|
||||||
val c = this.callCounter.get()
|
|
||||||
if (c == 0L) {
|
|
||||||
throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
|
|
||||||
}
|
|
||||||
if (c == Long.MAX_VALUE) {
|
|
||||||
throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
|
|
||||||
}
|
|
||||||
} while (! this.callCounter.compareAndSet(c, c + 1L))
|
|
||||||
// Now we can safely do the method call without the pointer being freed concurrently.
|
|
||||||
try {
|
|
||||||
return block(this.pointer)
|
|
||||||
} finally {
|
|
||||||
// This decrement aways matches the increment we performed above.
|
|
||||||
if (this.callCounter.decrementAndGet() == 0L) {
|
|
||||||
this.freeRustArcPtr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Public interface members begin here.
|
|
||||||
// Public facing enums
|
|
||||||
// Error definitions
|
|
||||||
@Structure.FieldOrder("code", "error_buf")
|
|
||||||
internal open class RustCallStatus : Structure() {
|
|
||||||
@JvmField var code: Int = 0
|
|
||||||
@JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue()
|
|
||||||
|
|
||||||
fun isSuccess(): Boolean {
|
|
||||||
return code == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isError(): Boolean {
|
|
||||||
return code == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isPanic(): Boolean {
|
|
||||||
return code == 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class InternalException(message: String) : Exception(message)
|
|
||||||
|
|
||||||
// Each top-level error class has a companion object that can lift the error from the call status's rust buffer
|
|
||||||
interface CallStatusErrorHandler<E> {
|
|
||||||
fun lift(error_buf: RustBuffer.ByValue): E;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers for calling Rust
|
|
||||||
// In practice we usually need to be synchronized to call this safely, so it doesn't
|
|
||||||
// synchronize itself
|
|
||||||
|
|
||||||
// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err
|
|
||||||
private inline fun <U, E: Exception> rustCallWithError(errorHandler: CallStatusErrorHandler<E>, callback: (RustCallStatus) -> U): U {
|
|
||||||
var status = RustCallStatus();
|
|
||||||
val return_value = callback(status)
|
|
||||||
if (status.isSuccess()) {
|
|
||||||
return return_value
|
|
||||||
} else if (status.isError()) {
|
|
||||||
throw errorHandler.lift(status.error_buf)
|
|
||||||
} else if (status.isPanic()) {
|
|
||||||
// when the rust code sees a panic, it tries to construct a rustbuffer
|
|
||||||
// with the message. but if that code panics, then it just sends back
|
|
||||||
// an empty buffer.
|
|
||||||
if (status.error_buf.len > 0) {
|
|
||||||
throw InternalException(String.lift(status.error_buf))
|
|
||||||
} else {
|
|
||||||
throw InternalException("Rust panic")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw InternalException("Unknown rust call status: $status.code")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR
|
|
||||||
object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> {
|
|
||||||
override fun lift(error_buf: RustBuffer.ByValue): InternalException {
|
|
||||||
RustBuffer.free(error_buf)
|
|
||||||
return InternalException("Unexpected CALL_ERROR")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call a rust function that returns a plain value
|
|
||||||
private inline fun <U> rustCall(callback: (RustCallStatus) -> U): U {
|
|
||||||
return rustCallWithError(NullCallStatusErrorHandler, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public facing records
|
|
||||||
|
|
||||||
// Namespace functions
|
|
||||||
|
|
||||||
|
|
||||||
// Objects
|
|
||||||
|
|
||||||
|
|
||||||
public interface OfflineWalletInterface {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class OfflineWallet(
|
|
||||||
pointer: Pointer
|
|
||||||
) : FFIObject(pointer), OfflineWalletInterface {
|
|
||||||
constructor(descriptor: String ) :
|
|
||||||
this(
|
|
||||||
rustCall() { status ->
|
|
||||||
_UniFFILib.INSTANCE.bdk_ed55_OfflineWallet_new(descriptor.lower() ,status)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnect the object from the underlying Rust object.
|
|
||||||
*
|
|
||||||
* It can be called more than once, but once called, interacting with the object
|
|
||||||
* causes an `IllegalStateException`.
|
|
||||||
*
|
|
||||||
* Clients **must** call this method once done with the object, or cause a memory leak.
|
|
||||||
*/
|
|
||||||
override protected fun freeRustArcPtr() {
|
|
||||||
rustCall() { status ->
|
|
||||||
_UniFFILib.INSTANCE.ffi_bdk_ed55_OfflineWallet_object_free(this.pointer, status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun lower(): Pointer = callWithPointer { it }
|
|
||||||
|
|
||||||
internal fun write(buf: RustBufferBuilder) {
|
|
||||||
// The Rust code always expects pointers written as 8 bytes,
|
|
||||||
// and will fail to compile if they don't fit.
|
|
||||||
buf.putLong(Pointer.nativeValue(this.lower()))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
internal fun lift(ptr: Pointer): OfflineWallet {
|
|
||||||
return OfflineWallet(ptr)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun read(buf: ByteBuffer): OfflineWallet {
|
|
||||||
// The Rust code always writes pointers as 8 bytes, and will
|
|
||||||
// fail to compile if they don't fit.
|
|
||||||
return OfflineWallet.lift(Pointer(buf.getLong()))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Callback Interfaces
|
|
||||||
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
|||||||
use ::safer_ffi::prelude::*;
|
|
||||||
use bdk::blockchain::{AnyBlockchainConfig, ElectrumBlockchainConfig};
|
|
||||||
use safer_ffi::boxed::Box;
|
|
||||||
use safer_ffi::char_p::char_p_ref;
|
|
||||||
|
|
||||||
#[derive_ReprC]
|
|
||||||
#[ReprC::opaque]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BlockchainConfig {
|
|
||||||
pub raw: AnyBlockchainConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn new_electrum_config(
|
|
||||||
url: char_p_ref,
|
|
||||||
socks5: Option<char_p_ref>,
|
|
||||||
retry: i16,
|
|
||||||
timeout: i16,
|
|
||||||
stop_gap: usize,
|
|
||||||
) -> Box<BlockchainConfig> {
|
|
||||||
let url = url.to_string();
|
|
||||||
let socks5 = socks5.map(|s| s.to_string());
|
|
||||||
let retry = short_to_u8(retry);
|
|
||||||
let timeout = short_to_optional_u8(timeout);
|
|
||||||
|
|
||||||
let electrum_config = AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig {
|
|
||||||
url,
|
|
||||||
socks5,
|
|
||||||
retry,
|
|
||||||
timeout,
|
|
||||||
stop_gap,
|
|
||||||
});
|
|
||||||
Box::new(BlockchainConfig {
|
|
||||||
raw: electrum_config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn free_blockchain_config(blockchain_config: Box<BlockchainConfig>) {
|
|
||||||
drop(blockchain_config);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO compact_filter rocksdb not compiling on android, switch to sqlite?
|
|
||||||
//#[derive_ReprC]
|
|
||||||
//#[repr(C)]
|
|
||||||
//#[derive(Debug)]
|
|
||||||
//pub struct BitcoinPeerConfig {
|
|
||||||
// pub address: char_p_boxed,
|
|
||||||
// pub socks5: Option<char_p_boxed>,
|
|
||||||
// pub socks5_credentials: Option<Box<Tuple2<char_p_boxed, char_p_boxed>>>,
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//impl From<&BitcoinPeerConfig> for BdkBitcoinPeerConfig {
|
|
||||||
// fn from(config: &BitcoinPeerConfig) -> Self {
|
|
||||||
// let address = config.address.to_string();
|
|
||||||
// let socks5 = config.socks5.as_ref().map(|p| p.to_string());
|
|
||||||
// let socks5_credentials = config
|
|
||||||
// .socks5_credentials.as_ref()
|
|
||||||
// .map(|c| (c._0.to_string(), c._1.to_string()));
|
|
||||||
//
|
|
||||||
// BdkBitcoinPeerConfig {
|
|
||||||
// address,
|
|
||||||
// socks5: socks5,
|
|
||||||
// socks5_credentials: socks5_credentials,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//#[ffi_export]
|
|
||||||
//fn new_compact_filters_config<'lt>(
|
|
||||||
// peers: c_slice::Ref<'lt, BitcoinPeerConfig>,
|
|
||||||
// network: char_p_ref,
|
|
||||||
// storage_dir: char_p_ref,
|
|
||||||
// skip_blocks: usize,
|
|
||||||
//) -> Box<BlockchainConfig> {
|
|
||||||
// let peers = peers.iter().map(|p| p.into()).collect();
|
|
||||||
// let network = Network::from_str(network.to_str()).unwrap();
|
|
||||||
// let storage_dir = storage_dir.to_string();
|
|
||||||
// let skip_blocks = Some(skip_blocks);
|
|
||||||
// let cf_config = AnyBlockchainConfig::CompactFilters(CompactFiltersBlockchainConfig {
|
|
||||||
// peers,
|
|
||||||
// network,
|
|
||||||
// storage_dir,
|
|
||||||
// skip_blocks,
|
|
||||||
// });
|
|
||||||
// Box::new(BlockchainConfig { raw: cf_config })
|
|
||||||
//}
|
|
||||||
|
|
||||||
// utility functions
|
|
||||||
|
|
||||||
fn short_to_optional_u8(short: i16) -> Option<u8> {
|
|
||||||
if short < 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(short_to_u8(short))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn short_to_u8(short: i16) -> u8 {
|
|
||||||
if short < 0 {
|
|
||||||
u8::MIN
|
|
||||||
} else {
|
|
||||||
u8::try_from(short).unwrap_or(u8::MAX)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
use ::safer_ffi::prelude::*;
|
|
||||||
use bdk::database::any::SledDbConfiguration;
|
|
||||||
use bdk::database::AnyDatabaseConfig;
|
|
||||||
use safer_ffi::boxed::Box;
|
|
||||||
use safer_ffi::char_p::char_p_ref;
|
|
||||||
|
|
||||||
#[derive_ReprC]
|
|
||||||
#[ReprC::opaque]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DatabaseConfig {
|
|
||||||
pub raw: AnyDatabaseConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn new_memory_config() -> Box<DatabaseConfig> {
|
|
||||||
let memory_config = AnyDatabaseConfig::Memory(());
|
|
||||||
Box::new(DatabaseConfig { raw: memory_config })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn new_sled_config(path: char_p_ref, tree_name: char_p_ref) -> Box<DatabaseConfig> {
|
|
||||||
let path = path.to_string();
|
|
||||||
let tree_name = tree_name.to_string();
|
|
||||||
|
|
||||||
let sled_config = AnyDatabaseConfig::Sled(SledDbConfiguration { path, tree_name });
|
|
||||||
Box::new(DatabaseConfig { raw: sled_config })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn free_database_config(database_config: Box<DatabaseConfig>) {
|
|
||||||
drop(database_config);
|
|
||||||
}
|
|
@ -1,175 +0,0 @@
|
|||||||
use std::convert::TryFrom;
|
|
||||||
use std::ffi::CString;
|
|
||||||
|
|
||||||
use ::safer_ffi::prelude::*;
|
|
||||||
use bdk::blockchain::{log_progress, AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain};
|
|
||||||
use bdk::database::{AnyDatabase, AnyDatabaseConfig, ConfigurableDatabase};
|
|
||||||
use bdk::wallet::AddressIndex::New;
|
|
||||||
use bdk::{Error, Wallet};
|
|
||||||
use safer_ffi::boxed::Box;
|
|
||||||
use safer_ffi::char_p::{char_p_boxed, char_p_ref};
|
|
||||||
|
|
||||||
use blockchain::BlockchainConfig;
|
|
||||||
use database::DatabaseConfig;
|
|
||||||
|
|
||||||
use crate::error::FfiError;
|
|
||||||
use crate::types::{FfiResult, FfiResultVoid};
|
|
||||||
use crate::wallet::transaction::{LocalUtxo, TransactionDetails};
|
|
||||||
use bdk::bitcoin::Network;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
mod blockchain;
|
|
||||||
mod database;
|
|
||||||
mod transaction;
|
|
||||||
|
|
||||||
// create a new wallet
|
|
||||||
|
|
||||||
#[derive_ReprC]
|
|
||||||
#[ReprC::opaque]
|
|
||||||
pub struct OpaqueWallet {
|
|
||||||
raw: Wallet<AnyBlockchain, AnyDatabase>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn new_wallet_result(
|
|
||||||
descriptor: char_p_ref,
|
|
||||||
change_descriptor: Option<char_p_ref>,
|
|
||||||
network: char_p_ref,
|
|
||||||
blockchain_config: &BlockchainConfig,
|
|
||||||
database_config: &DatabaseConfig,
|
|
||||||
) -> FfiResult<Option<Box<OpaqueWallet>>> {
|
|
||||||
let descriptor = descriptor.to_string();
|
|
||||||
let change_descriptor = change_descriptor.map(|s| s.to_string());
|
|
||||||
let net = Network::from_str(network.to_str()).expect("Network name");
|
|
||||||
let bc_config = &blockchain_config.raw;
|
|
||||||
let db_config = &database_config.raw;
|
|
||||||
let wallet_result = new_wallet(descriptor, change_descriptor, net, bc_config, db_config);
|
|
||||||
|
|
||||||
match wallet_result {
|
|
||||||
Ok(w) => FfiResult {
|
|
||||||
ok: Some(Box::new(OpaqueWallet { raw: w })),
|
|
||||||
err: FfiError::None,
|
|
||||||
},
|
|
||||||
Err(e) => FfiResult {
|
|
||||||
ok: None,
|
|
||||||
err: FfiError::from(&e),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_wallet(
|
|
||||||
descriptor: String,
|
|
||||||
change_descriptor: Option<String>,
|
|
||||||
network: Network,
|
|
||||||
blockchain_config: &AnyBlockchainConfig,
|
|
||||||
database_config: &AnyDatabaseConfig,
|
|
||||||
) -> Result<Wallet<AnyBlockchain, AnyDatabase>, Error> {
|
|
||||||
let client = AnyBlockchain::from_config(blockchain_config)?;
|
|
||||||
let database = AnyDatabase::from_config(database_config)?;
|
|
||||||
|
|
||||||
let descriptor: &str = descriptor.as_str();
|
|
||||||
let change_descriptor: Option<&str> = change_descriptor.as_deref();
|
|
||||||
|
|
||||||
Wallet::new(descriptor, change_descriptor, network, database, client)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn free_wallet_result(wallet_result: FfiResult<Option<Box<OpaqueWallet>>>) {
|
|
||||||
drop(wallet_result);
|
|
||||||
}
|
|
||||||
|
|
||||||
// wallet operations
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn sync_wallet(opaque_wallet: &OpaqueWallet) -> FfiResultVoid {
|
|
||||||
let int_result = opaque_wallet.raw.sync(log_progress(), Some(100));
|
|
||||||
match int_result {
|
|
||||||
Ok(_v) => FfiResultVoid {
|
|
||||||
err: FfiError::None,
|
|
||||||
},
|
|
||||||
Err(e) => FfiResultVoid {
|
|
||||||
err: FfiError::from(&e),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn new_address(opaque_wallet: &OpaqueWallet) -> FfiResult<char_p_boxed> {
|
|
||||||
let new_address = opaque_wallet.raw.get_address(New);
|
|
||||||
let string_result = new_address.map(|a| a.to_string());
|
|
||||||
match string_result {
|
|
||||||
Ok(a) => FfiResult {
|
|
||||||
ok: char_p_boxed::try_from(a).unwrap(),
|
|
||||||
err: FfiError::None,
|
|
||||||
},
|
|
||||||
Err(e) => FfiResult {
|
|
||||||
ok: char_p_boxed::from(CString::default()),
|
|
||||||
err: FfiError::from(&e),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn list_unspent(opaque_wallet: &OpaqueWallet) -> FfiResult<repr_c::Vec<LocalUtxo>> {
|
|
||||||
let unspent_result = opaque_wallet.raw.list_unspent();
|
|
||||||
|
|
||||||
match unspent_result {
|
|
||||||
Ok(v) => FfiResult {
|
|
||||||
ok: {
|
|
||||||
let ve: Vec<LocalUtxo> = v.iter().map(|lu| LocalUtxo::from(lu)).collect();
|
|
||||||
repr_c::Vec::from(ve)
|
|
||||||
},
|
|
||||||
err: FfiError::None,
|
|
||||||
},
|
|
||||||
Err(e) => FfiResult {
|
|
||||||
ok: repr_c::Vec::EMPTY,
|
|
||||||
err: FfiError::from(&e),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn free_veclocalutxo_result(unspent_result: FfiResult<repr_c::Vec<LocalUtxo>>) {
|
|
||||||
drop(unspent_result)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn balance(opaque_wallet: &OpaqueWallet) -> FfiResult<u64> {
|
|
||||||
let balance_result = opaque_wallet.raw.get_balance();
|
|
||||||
|
|
||||||
match balance_result {
|
|
||||||
Ok(b) => FfiResult {
|
|
||||||
ok: b,
|
|
||||||
err: FfiError::None,
|
|
||||||
},
|
|
||||||
Err(e) => FfiResult {
|
|
||||||
ok: u64::MIN,
|
|
||||||
err: FfiError::from(&e),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn list_transactions(opaque_wallet: &OpaqueWallet) -> FfiResult<repr_c::Vec<TransactionDetails>> {
|
|
||||||
let transactions_result = opaque_wallet.raw.list_transactions(false);
|
|
||||||
|
|
||||||
match transactions_result {
|
|
||||||
Ok(v) => FfiResult {
|
|
||||||
ok: {
|
|
||||||
let ve: Vec<TransactionDetails> =
|
|
||||||
v.iter().map(|t| TransactionDetails::from(t)).collect();
|
|
||||||
repr_c::Vec::from(ve)
|
|
||||||
},
|
|
||||||
err: FfiError::None,
|
|
||||||
},
|
|
||||||
Err(e) => FfiResult {
|
|
||||||
ok: repr_c::Vec::EMPTY,
|
|
||||||
err: FfiError::from(&e),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn free_vectxdetails_result(txdetails_result: FfiResult<repr_c::Vec<TransactionDetails>>) {
|
|
||||||
drop(txdetails_result)
|
|
||||||
}
|
|
@ -1,124 +0,0 @@
|
|||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
use ::safer_ffi::prelude::*;
|
|
||||||
use safer_ffi::char_p::char_p_boxed;
|
|
||||||
|
|
||||||
// Non-opaque returned values
|
|
||||||
|
|
||||||
#[derive_ReprC]
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct TransactionDetails {
|
|
||||||
// TODO Optional transaction
|
|
||||||
// pub transaction: Option<Transaction>,
|
|
||||||
/// Transaction id
|
|
||||||
pub txid: char_p_boxed,
|
|
||||||
/// Received value (sats)
|
|
||||||
pub received: u64,
|
|
||||||
/// Sent value (sats)
|
|
||||||
pub sent: u64,
|
|
||||||
/// Fee value (sats) if known, -1 if unknown, based on backend
|
|
||||||
pub fee: i64,
|
|
||||||
/// true if confirmed
|
|
||||||
pub is_confirmed: bool,
|
|
||||||
/// Confirmed in block height
|
|
||||||
pub confirmation_time: ConfirmationTime,
|
|
||||||
/// Whether the tx has been verified against the consensus rules
|
|
||||||
pub verified: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive_ReprC]
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ConfirmationTime {
|
|
||||||
/// confirmation block height, 0 if is_confirmed is false
|
|
||||||
pub height: u32,
|
|
||||||
/// confirmation block timestamp, 0 if is_confirmed is false
|
|
||||||
pub timestamp: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&bdk::TransactionDetails> for TransactionDetails {
|
|
||||||
fn from(op: &bdk::TransactionDetails) -> Self {
|
|
||||||
let fee = op.fee.map(|f| i64::try_from(f).unwrap()).unwrap_or(-1);
|
|
||||||
let confirmation_time = op
|
|
||||||
.confirmation_time
|
|
||||||
.as_ref()
|
|
||||||
.map(|c| ConfirmationTime {
|
|
||||||
height: c.height,
|
|
||||||
timestamp: c.timestamp,
|
|
||||||
})
|
|
||||||
.unwrap_or(ConfirmationTime {
|
|
||||||
height: 0,
|
|
||||||
timestamp: 0,
|
|
||||||
});
|
|
||||||
TransactionDetails {
|
|
||||||
txid: char_p_boxed::try_from(op.txid.to_string()).unwrap(),
|
|
||||||
received: op.received,
|
|
||||||
sent: op.sent,
|
|
||||||
fee,
|
|
||||||
is_confirmed: op.confirmation_time.is_some(),
|
|
||||||
confirmation_time,
|
|
||||||
verified: op.verified,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive_ReprC]
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct OutPoint {
|
|
||||||
/// The referenced transaction's txid, as hex string
|
|
||||||
pub txid: char_p_boxed,
|
|
||||||
/// The index of the referenced output in its transaction's vout
|
|
||||||
pub vout: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&bdk::bitcoin::OutPoint> for OutPoint {
|
|
||||||
fn from(op: &bdk::bitcoin::OutPoint) -> Self {
|
|
||||||
OutPoint {
|
|
||||||
txid: char_p_boxed::try_from(op.txid.to_string()).unwrap(),
|
|
||||||
vout: op.vout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive_ReprC]
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct TxOut {
|
|
||||||
/// The value of the output, in satoshis
|
|
||||||
pub value: u64,
|
|
||||||
/// The script which must satisfy for the output to be spent, as hex string
|
|
||||||
pub script_pubkey: char_p_boxed,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&bdk::bitcoin::TxOut> for TxOut {
|
|
||||||
fn from(to: &bdk::bitcoin::TxOut) -> Self {
|
|
||||||
TxOut {
|
|
||||||
value: to.value,
|
|
||||||
script_pubkey: char_p_boxed::try_from(to.script_pubkey.to_string()).unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive_ReprC]
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct LocalUtxo {
|
|
||||||
/// Reference to a transaction output
|
|
||||||
pub outpoint: OutPoint,
|
|
||||||
/// Transaction output
|
|
||||||
pub txout: TxOut,
|
|
||||||
/// Type of keychain, as short 0 for "external" or 1 for "internal"
|
|
||||||
pub keychain: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&bdk::LocalUtxo> for LocalUtxo {
|
|
||||||
fn from(lu: &bdk::LocalUtxo) -> Self {
|
|
||||||
LocalUtxo {
|
|
||||||
outpoint: OutPoint::from(&lu.outpoint),
|
|
||||||
txout: TxOut::from(&lu.txout),
|
|
||||||
keychain: lu.keychain as u16,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
# Project-wide Gradle settings.
|
|
||||||
# IDE (e.g. Android Studio) users:
|
|
||||||
# Gradle settings configured through the IDE *will override*
|
|
||||||
# any settings specified in this file.
|
|
||||||
# For more details on how to configure your build environment visit
|
|
||||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
|
||||||
org.gradle.jvmargs=-Xmx1536m
|
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
|
||||||
# org.gradle.parallel=true
|
|
||||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
|
||||||
# Android operating system, and which are packaged with your app's APK
|
|
||||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
|
||||||
android.useAndroidX=true
|
|
||||||
# Automatically convert third-party libraries to use AndroidX
|
|
||||||
android.enableJetifier=true
|
|
||||||
# Kotlin code style for this project: "official" or "obsolete":
|
|
||||||
kotlin.code.style=official
|
|
||||||
jna.debug_load=true
|
|
||||||
jna.debug_load.jna=true
|
|
Loading…
x
Reference in New Issue
Block a user