From b437b786689da52c781d046e3024561e43895f04 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Sun, 4 Jul 2021 22:10:16 -0700 Subject: [PATCH] Add Wallet.listTransactions() --- .../kotlin/org/bitcoindevkit/bdk/LibJna.kt | 115 ++++++++++++++++-- .../bitcoindevkit/bdk/types/StringResult.kt | 4 +- .../bitcoindevkit/bdk/types/UInt64Result.kt | 4 +- .../org/bitcoindevkit/bdk/types/VoidResult.kt | 2 +- .../bdk/wallet/VecLocalUtxoResult.kt | 4 +- .../bdk/wallet/VecTxDetailsResult.kt | 32 +++++ .../org/bitcoindevkit/bdk/wallet/Wallet.kt | 5 + .../bitcoindevkit/bdk/wallet/WalletResult.kt | 4 +- .../kotlin/org/bitcoindevkit/bdk/LibTest.kt | 22 ++++ cc/bdk_ffi.h | 43 +++++++ cc/bdk_ffi_test.c | 47 +++++++ src/wallet/mod.rs | 77 ++++-------- src/wallet/transaction.rs | 99 +++++++++++++++ 13 files changed, 384 insertions(+), 74 deletions(-) create mode 100644 bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecTxDetailsResult.kt create mode 100644 src/wallet/transaction.rs diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt index 98b5a19..3fd4cbe 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/LibJna.kt @@ -16,10 +16,10 @@ interface LibJna : Library { class ByReference : FfiResult_char_ptr_t(), Structure.ByReference @JvmField - var ok: String = "" + var ok: String? = null @JvmField - var err: Short = 0 + var err: Short? = null override fun getFieldOrder() = listOf("ok", "err") } @@ -38,7 +38,7 @@ interface LibJna : Library { class ByReference : FfiResultVoid_t(), Structure.ByReference @JvmField - var err: Short = 0 + var err: Short? = null override fun getFieldOrder() = listOf("err") } @@ -97,10 +97,10 @@ interface LibJna : Library { class ByReference : FfiResult_OpaqueWallet_ptr_t(), Structure.ByReference @JvmField - var ok: OpaqueWallet_t = OpaqueWallet_t() + var ok: OpaqueWallet_t? = null @JvmField - var err: Short = 0 + var err: Short? = null override fun getFieldOrder() = listOf("ok", "err") } @@ -224,10 +224,10 @@ interface LibJna : Library { class ByReference : FfiResultVec_LocalUtxo_t(), Structure.ByReference @JvmField - var ok: Vec_LocalUtxo_t = Vec_LocalUtxo_t() + var ok: Vec_LocalUtxo_t? = null @JvmField - var err: Short = 0 + var err: Short? = null override fun getFieldOrder() = listOf("ok", "err") } @@ -249,10 +249,10 @@ interface LibJna : Library { class ByReference : FfiResult_uint64_t(), Structure.ByReference @JvmField - var ok: Long = Long.MIN_VALUE + var ok: Long? = null @JvmField - var err: Short = 0 + var err: Short? = null override fun getFieldOrder() = listOf("ok", "err") } @@ -288,4 +288,101 @@ interface LibJna : Library { // void free_database_config ( // DatabaseConfig_t * database_config); fun free_database_config(database_config: DatabaseConfig_t) + + // typedef struct { + // + // char * txid; + // + // uint64_t timestamp; + // + // uint64_t received; + // + // uint64_t sent; + // + // uint64_t fees; + // + // int32_t height; + // + // } 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 timestamp: Long? = null + + @JvmField + var received: Long? = null + + @JvmField + var sent: Long? = null + + @JvmField + var fees: Long? = null + + @JvmField + var height: Int? = null + + override fun getFieldOrder() = listOf("txid", "timestamp", "received", "sent", "fees", "height") + } + + // 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) } diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt index 37d86a7..2d7ac46 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/StringResult.kt @@ -12,8 +12,8 @@ class StringResult constructor(private val ffiResultCharPtrT: LibJna.FfiResult_c private val log: Logger = LoggerFactory.getLogger(StringResult::class.java) fun value(): String { - val err = ffiResultCharPtrT.err - val ok = ffiResultCharPtrT.ok + val err = ffiResultCharPtrT.err!! + val ok = ffiResultCharPtrT.ok!! when { err > 0 -> { throw FfiException(err) diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/UInt64Result.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/UInt64Result.kt index 43a7eb7..1b4e6f8 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/UInt64Result.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/UInt64Result.kt @@ -12,8 +12,8 @@ class UInt64Result constructor(private val ffiResultUint64T: LibJna.FfiResult_ui private val log: Logger = LoggerFactory.getLogger(UInt64Result::class.java) fun value(): Long { - val err = ffiResultUint64T.err - val ok = ffiResultUint64T.ok + val err = ffiResultUint64T.err!! + val ok = ffiResultUint64T.ok!! when { err > 0 -> { throw FfiException(err) diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt index 6b9b685..c50b328 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/types/VoidResult.kt @@ -12,7 +12,7 @@ class VoidResult constructor(private val ffiResultVoidT: LibJna.FfiResultVoid_t. private val log: Logger = LoggerFactory.getLogger(VoidResult::class.java) fun value(): Unit { - val err = ffiResultVoidT.err + val err = ffiResultVoidT.err!! when { err > 0 -> { diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt index 4f48d45..8a005f3 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecLocalUtxoResult.kt @@ -12,8 +12,8 @@ class VecLocalUtxoResult(private val ffiResultVecLocalUtxoT: LibJna.FfiResultVec private val log: Logger = LoggerFactory.getLogger(VecLocalUtxoResult::class.java) fun value(): Array { - val err = ffiResultVecLocalUtxoT.err - val ok = ffiResultVecLocalUtxoT.ok + val err = ffiResultVecLocalUtxoT.err!! + val ok = ffiResultVecLocalUtxoT.ok!! when { err > 0 -> { throw FfiException(err) diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecTxDetailsResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecTxDetailsResult.kt new file mode 100644 index 0000000..abee9a2 --- /dev/null +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/VecTxDetailsResult.kt @@ -0,0 +1,32 @@ +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 { + 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 + } + } + } + + protected fun finalize() { + libJna.free_vectxdetails_result(ffiResultVecTransactionDetailsT) + log.debug("$ffiResultVecTransactionDetailsT freed") + } +} \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt index 940dfda..9cd3346 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/Wallet.kt @@ -48,4 +48,9 @@ class Wallet constructor( val longResult = UInt64Result(libJna.balance(wallet)) return longResult.value() } + + fun listTransactionDetails(): Array { + val vecTxDetailsResult = VecTxDetailsResult(libJna.list_transactions((wallet))) + return vecTxDetailsResult.value() + } } \ No newline at end of file diff --git a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt index 2d43b0c..c574f27 100644 --- a/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt +++ b/bdk-kotlin/jvm/src/main/kotlin/org/bitcoindevkit/bdk/wallet/WalletResult.kt @@ -12,14 +12,14 @@ class WalletResult constructor(private val ffiResultOpaqueWalletPtrT: LibJna.Ffi private val log: Logger = LoggerFactory.getLogger(WalletResult::class.java) fun value(): LibJna.OpaqueWallet_t { - val err = ffiResultOpaqueWalletPtrT.err + val err = ffiResultOpaqueWalletPtrT.err!! val ok = ffiResultOpaqueWalletPtrT.ok when { err > 0 -> { throw FfiException(err) } else -> { - return ok + return ok!! } } } diff --git a/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt index 2ecfa69..739e348 100644 --- a/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/test-fixtures/src/main/kotlin/org/bitcoindevkit/bdk/LibTest.kt @@ -105,4 +105,26 @@ abstract class LibTest : LibBase() { //log.debug("balance from kotlin: $balance") assertTrue(balance > 0) } + + @Test + fun walletTxDetails() { + val wallet = Wallet(desc, change, 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.timestamp: ${it.timestamp}") + assertTrue(it.timestamp!! > 0) + //log.debug("txDetails.received: ${it.received}") + //log.debug("txDetails.sent: ${it.sent}") + assertTrue(it.received!! > 0 || it.sent!! > 0) + //log.debug("txDetails.fees: ${it.fees}") + assertTrue(it.fees!! > 0) + //log.debug("txDetails.fees: ${it.height}") + assertTrue(it.height!! >= -1) + } + } } diff --git a/cc/bdk_ffi.h b/cc/bdk_ffi.h index 89f0ea2..850e75a 100644 --- a/cc/bdk_ffi.h +++ b/cc/bdk_ffi.h @@ -214,6 +214,49 @@ typedef struct { FfiResult_uint64_t balance ( OpaqueWallet_t const * opaque_wallet); +typedef struct { + + char * txid; + + uint64_t timestamp; + + uint64_t received; + + uint64_t sent; + + uint64_t fees; + + int32_t height; + +} TransactionDetails_t; + +/** \brief + * Same as [`Vec`][`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, diff --git a/cc/bdk_ffi_test.c b/cc/bdk_ffi_test.c index 82d9417..5f95924 100644 --- a/cc/bdk_ffi_test.c +++ b/cc/bdk_ffi_test.c @@ -152,6 +152,53 @@ int main (int argc, char const * const argv[]) free_uint64_result(balance_result); free_wallet_result(wallet_result); } + + // test get transaction details + { + char const *desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; + char const *change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"; + + BlockchainConfig_t *bc_config = new_electrum_config("ssl://electrum.blockstream.info:60002", NULL, 5, 30); + DatabaseConfig_t *db_config = new_memory_config(); + + // new wallet + FfiResult_OpaqueWallet_ptr_t wallet_result = new_wallet_result(desc,change,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].timestamp > 0); + //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].fees > 0); + //printf("%d: height: %d\n", i, txdetails_ptr[i].height); + assert(txdetails_ptr[i].height >= -1); + } + + free_vectxdetails_result(txdetails_result); + free_wallet_result(wallet_result); + } return EXIT_SUCCESS; } diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index c395b23..90327eb 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -15,9 +15,11 @@ use database::DatabaseConfig; use crate::error::FfiError; use crate::types::{FfiResult, FfiResultVoid}; +use crate::wallet::transaction::{LocalUtxo, TransactionDetails}; mod blockchain; mod database; +mod transaction; // create a new wallet @@ -145,64 +147,27 @@ fn balance(opaque_wallet: &OpaqueWallet) -> FfiResult { } } -// Non-opaque returned values +#[ffi_export] +fn list_transactions(opaque_wallet: &OpaqueWallet) -> FfiResult> { + let transactions_result = opaque_wallet.raw.list_transactions(false); -#[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, - } + match transactions_result { + Ok(v) => FfiResult { + ok: { + let ve: Vec = + 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), + }, } } -#[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, - } - } +#[ffi_export] +fn free_vectxdetails_result(txdetails_result: FfiResult>) { + drop(txdetails_result) } diff --git a/src/wallet/transaction.rs b/src/wallet/transaction.rs new file mode 100644 index 0000000..378d4cf --- /dev/null +++ b/src/wallet/transaction.rs @@ -0,0 +1,99 @@ +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 id + pub txid: char_p_boxed, + /// Timestamp + pub timestamp: u64, + /// Received value (sats) + pub received: u64, + /// Sent value (sats) + pub sent: u64, + /// Fee value (sats) + pub fees: u64, + /// Confirmed in block height, `None` means unconfirmed + pub height: i32, +} + +impl From<&bdk::TransactionDetails> for TransactionDetails { + fn from(op: &bdk::TransactionDetails) -> Self { + TransactionDetails { + txid: char_p_boxed::try_from(op.txid.to_string()).unwrap(), + timestamp: op.timestamp, + received: op.received, + sent: op.sent, + fees: op.fees, + height: op.height.map(|h| h as i32).unwrap_or(-1), + } + } +} + +#[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, + } + } +}