Merge pull request #13 from notmandatory/allow-creating-a-wallet-transaction

Allow creating partially signed bitcoin transactions
This commit is contained in:
Sudarsan Balaji 2021-10-16 16:44:49 +05:30 committed by GitHub
commit 33a291f760
4 changed files with 139 additions and 37 deletions

View File

@ -2,24 +2,28 @@ import uniffi.bdk.*
class LogProgress: BdkProgress { class LogProgress: BdkProgress {
override fun update(progress: Float, message: String? ) { override fun update(progress: Float, message: String? ) {
println(progress); println("progress: $progress, message: $message")
println(message);
} }
} }
fun main(args: Array<String>) { fun main(args: Array<String>) {
println("Configuring an in-memory wallet on electrum..")
val descriptor = val descriptor =
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"; "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)";
val amount = 10000uL;
val recipient = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt";
val db = DatabaseConfig.Memory("") val db = DatabaseConfig.Memory("")
val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 100u)) val client = BlockchainConfig.Electrum(ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 100u))
val wallet = OnlineWallet(descriptor, Network.TESTNET, db, client) val wallet = OnlineWallet(descriptor, Network.TESTNET, db, client)
val address = wallet.getNewAddress() val address = wallet.getNewAddress()
println("Please send satoshis to wallet address: $address") println("Please send $amount satoshis to address: $address")
readLine() readLine()
println("Syncing...") println("Syncing...")
wallet.sync(LogProgress(), null) wallet.sync(LogProgress(), null)
val balance = wallet.getBalance() val balance = wallet.getBalance()
println("New wallet balance: $balance") println("New wallet balance: $balance")
println("Refunding $amount satoshis to $recipient")
val psbt = PartiallySignedBitcoinTransaction(wallet, recipient, amount)
println("Press any key to exit") println("Press any key to exit")
readLine() readLine()
} }

View File

@ -44,15 +44,15 @@ open class RustBuffer : Structure() {
companion object { companion object {
internal fun alloc(size: Int = 0) = rustCall() { status -> internal fun alloc(size: Int = 0) = rustCall() { status ->
_UniFFILib.INSTANCE.ffi_bdk_7046_rustbuffer_alloc(size, status) _UniFFILib.INSTANCE.ffi_bdk_b7c7_rustbuffer_alloc(size, status)
} }
internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> internal fun free(buf: RustBuffer.ByValue) = rustCall() { status ->
_UniFFILib.INSTANCE.ffi_bdk_7046_rustbuffer_free(buf, status) _UniFFILib.INSTANCE.ffi_bdk_b7c7_rustbuffer_free(buf, status)
} }
internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status -> internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall() { status ->
_UniFFILib.INSTANCE.ffi_bdk_7046_rustbuffer_reserve(buf, additional, status) _UniFFILib.INSTANCE.ffi_bdk_b7c7_rustbuffer_reserve(buf, additional, status)
} }
} }
@ -392,6 +392,12 @@ internal fun String.write(buf: RustBufferBuilder) {
@ -542,59 +548,67 @@ internal interface _UniFFILib : Library {
} }
} }
fun ffi_bdk_7046_OfflineWallet_object_free(ptr: Pointer, fun ffi_bdk_b7c7_OfflineWallet_object_free(ptr: Pointer,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): Unit ): Unit
fun bdk_7046_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue, fun bdk_b7c7_OfflineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): Pointer ): Pointer
fun bdk_7046_OfflineWallet_get_new_address(ptr: Pointer, fun bdk_b7c7_OfflineWallet_get_new_address(ptr: Pointer,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): RustBuffer.ByValue ): RustBuffer.ByValue
fun ffi_bdk_7046_OnlineWallet_object_free(ptr: Pointer, fun ffi_bdk_b7c7_OnlineWallet_object_free(ptr: Pointer,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): Unit ): Unit
fun bdk_7046_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue, fun bdk_b7c7_OnlineWallet_new(descriptor: RustBuffer.ByValue,network: RustBuffer.ByValue,database_config: RustBuffer.ByValue,blockchain_config: RustBuffer.ByValue,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): Pointer ): Pointer
fun bdk_7046_OnlineWallet_get_new_address(ptr: Pointer, fun bdk_b7c7_OnlineWallet_get_new_address(ptr: Pointer,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): RustBuffer.ByValue ): RustBuffer.ByValue
fun bdk_7046_OnlineWallet_get_network(ptr: Pointer, fun bdk_b7c7_OnlineWallet_get_network(ptr: Pointer,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): RustBuffer.ByValue ): RustBuffer.ByValue
fun bdk_7046_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue, fun bdk_b7c7_OnlineWallet_sync(ptr: Pointer,progress_update: Long,max_address_param: RustBuffer.ByValue,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): Unit ): Unit
fun bdk_7046_OnlineWallet_get_balance(ptr: Pointer, fun bdk_b7c7_OnlineWallet_get_balance(ptr: Pointer,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): Long ): Long
fun ffi_bdk_7046_BdkProgress_init_callback(callback_stub: ForeignCallback, fun ffi_bdk_b7c7_PartiallySignedBitcoinTransaction_object_free(ptr: Pointer,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): Unit ): Unit
fun ffi_bdk_7046_rustbuffer_alloc(size: Int, fun bdk_b7c7_PartiallySignedBitcoinTransaction_new(wallet: Pointer,recipient: RustBuffer.ByValue,amount: Long,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): RustBuffer.ByValue ): Pointer
fun ffi_bdk_7046_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue, fun ffi_bdk_b7c7_BdkProgress_init_callback(callback_stub: ForeignCallback,
uniffi_out_err: RustCallStatus
): RustBuffer.ByValue
fun ffi_bdk_7046_rustbuffer_free(buf: RustBuffer.ByValue,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): Unit ): Unit
fun ffi_bdk_7046_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int, fun ffi_bdk_b7c7_rustbuffer_alloc(size: Int,
uniffi_out_err: RustCallStatus
): RustBuffer.ByValue
fun ffi_bdk_b7c7_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue,
uniffi_out_err: RustCallStatus
): RustBuffer.ByValue
fun ffi_bdk_b7c7_rustbuffer_free(buf: RustBuffer.ByValue,
uniffi_out_err: RustCallStatus
): Unit
fun ffi_bdk_b7c7_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int,
uniffi_out_err: RustCallStatus uniffi_out_err: RustCallStatus
): RustBuffer.ByValue ): RustBuffer.ByValue
@ -1304,7 +1318,7 @@ class OfflineWallet(
constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) : constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig ) :
this( this(
rustCallWithError(BdkException) { status -> rustCallWithError(BdkException) { status ->
_UniFFILib.INSTANCE.bdk_7046_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status) _UniFFILib.INSTANCE.bdk_b7c7_OfflineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower() ,status)
}) })
/** /**
@ -1317,7 +1331,7 @@ class OfflineWallet(
*/ */
override protected fun freeRustArcPtr() { override protected fun freeRustArcPtr() {
rustCall() { status -> rustCall() { status ->
_UniFFILib.INSTANCE.ffi_bdk_7046_OfflineWallet_object_free(this.pointer, status) _UniFFILib.INSTANCE.ffi_bdk_b7c7_OfflineWallet_object_free(this.pointer, status)
} }
} }
@ -1332,7 +1346,7 @@ class OfflineWallet(
override fun getNewAddress(): String = override fun getNewAddress(): String =
callWithPointer { callWithPointer {
rustCall() { status -> rustCall() { status ->
_UniFFILib.INSTANCE.bdk_7046_OfflineWallet_get_new_address(it, status) _UniFFILib.INSTANCE.bdk_b7c7_OfflineWallet_get_new_address(it, status)
} }
}.let { }.let {
String.lift(it) String.lift(it)
@ -1371,7 +1385,7 @@ class OnlineWallet(
constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) : constructor(descriptor: String, network: Network, databaseConfig: DatabaseConfig, blockchainConfig: BlockchainConfig ) :
this( this(
rustCallWithError(BdkException) { status -> rustCallWithError(BdkException) { status ->
_UniFFILib.INSTANCE.bdk_7046_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status) _UniFFILib.INSTANCE.bdk_b7c7_OnlineWallet_new(descriptor.lower(), network.lower(), databaseConfig.lower(), blockchainConfig.lower() ,status)
}) })
/** /**
@ -1384,7 +1398,7 @@ class OnlineWallet(
*/ */
override protected fun freeRustArcPtr() { override protected fun freeRustArcPtr() {
rustCall() { status -> rustCall() { status ->
_UniFFILib.INSTANCE.ffi_bdk_7046_OnlineWallet_object_free(this.pointer, status) _UniFFILib.INSTANCE.ffi_bdk_b7c7_OnlineWallet_object_free(this.pointer, status)
} }
} }
@ -1399,7 +1413,7 @@ class OnlineWallet(
override fun getNewAddress(): String = override fun getNewAddress(): String =
callWithPointer { callWithPointer {
rustCall() { status -> rustCall() { status ->
_UniFFILib.INSTANCE.bdk_7046_OnlineWallet_get_new_address(it, status) _UniFFILib.INSTANCE.bdk_b7c7_OnlineWallet_get_new_address(it, status)
} }
}.let { }.let {
String.lift(it) String.lift(it)
@ -1408,7 +1422,7 @@ class OnlineWallet(
override fun getNetwork(): Network = override fun getNetwork(): Network =
callWithPointer { callWithPointer {
rustCall() { status -> rustCall() { status ->
_UniFFILib.INSTANCE.bdk_7046_OnlineWallet_get_network(it, status) _UniFFILib.INSTANCE.bdk_b7c7_OnlineWallet_get_network(it, status)
} }
}.let { }.let {
Network.lift(it) Network.lift(it)
@ -1417,14 +1431,14 @@ class OnlineWallet(
override fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) = override fun sync(progressUpdate: BdkProgress, maxAddressParam: UInt? ) =
callWithPointer { callWithPointer {
rustCallWithError(BdkException) { status -> rustCallWithError(BdkException) { status ->
_UniFFILib.INSTANCE.bdk_7046_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status) _UniFFILib.INSTANCE.bdk_b7c7_OnlineWallet_sync(it, CallbackInterfaceBdkProgressInternals.lower(progressUpdate), lowerOptionalu32(maxAddressParam) , status)
} }
} }
override fun getBalance(): ULong = override fun getBalance(): ULong =
callWithPointer { callWithPointer {
rustCallWithError(BdkException) { status -> rustCallWithError(BdkException) { status ->
_UniFFILib.INSTANCE.bdk_7046_OnlineWallet_get_balance(it, status) _UniFFILib.INSTANCE.bdk_b7c7_OnlineWallet_get_balance(it, status)
} }
}.let { }.let {
ULong.lift(it) ULong.lift(it)
@ -1447,6 +1461,60 @@ class OnlineWallet(
} }
} }
@ExperimentalUnsignedTypes
public interface PartiallySignedBitcoinTransactionInterface {
}
@ExperimentalUnsignedTypes
class PartiallySignedBitcoinTransaction(
pointer: Pointer
) : FFIObject(pointer), PartiallySignedBitcoinTransactionInterface {
constructor(wallet: OnlineWallet, recipient: String, amount: ULong ) :
this(
rustCallWithError(BdkException) { status ->
_UniFFILib.INSTANCE.bdk_b7c7_PartiallySignedBitcoinTransaction_new(wallet.lower(), recipient.lower(), amount.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_b7c7_PartiallySignedBitcoinTransaction_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): PartiallySignedBitcoinTransaction {
return PartiallySignedBitcoinTransaction(ptr)
}
internal fun read(buf: ByteBuffer): PartiallySignedBitcoinTransaction {
// The Rust code always writes pointers as 8 bytes, and will
// fail to compile if they don't fit.
return PartiallySignedBitcoinTransaction.lift(Pointer(buf.getLong()))
}
}
}
// Callback Interfaces // Callback Interfaces
@ -1496,7 +1564,7 @@ internal object CallbackInterfaceBdkProgressInternals: CallbackInternals<BdkProg
) { ) {
override fun register(lib: _UniFFILib) { override fun register(lib: _UniFFILib) {
rustCall() { status -> rustCall() { status ->
lib.ffi_bdk_7046_BdkProgress_init_callback(this.foreignCallback, status) lib.ffi_bdk_b7c7_BdkProgress_init_callback(this.foreignCallback, status)
} }
} }
} }

View File

@ -105,3 +105,8 @@ interface OnlineWallet {
[Throws=BdkError] [Throws=BdkError]
u64 get_balance(); u64 get_balance();
}; };
interface PartiallySignedBitcoinTransaction {
[Throws=BdkError]
constructor([ByRef] OnlineWallet wallet, string recipient, u64 amount);
};

View File

@ -1,4 +1,6 @@
use bdk::bitcoin::Network; use bdk::address_validator::AddressValidatorError;
use bdk::bitcoin::util::psbt::PartiallySignedTransaction;
use bdk::bitcoin::{Address, Network};
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig}; use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
use bdk::blockchain::Progress; use bdk::blockchain::Progress;
use bdk::blockchain::{ use bdk::blockchain::{
@ -7,9 +9,9 @@ use bdk::blockchain::{
use bdk::database::any::{AnyDatabase, SledDbConfiguration}; use bdk::database::any::{AnyDatabase, SledDbConfiguration};
use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase}; use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase};
use bdk::wallet::AddressIndex; use bdk::wallet::AddressIndex;
use bdk::Error; use bdk::{Error, Wallet};
use bdk::Wallet;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::str::FromStr;
use std::sync::{Mutex, MutexGuard}; use std::sync::{Mutex, MutexGuard};
uniffi_macros::include_scaffolding!("bdk"); uniffi_macros::include_scaffolding!("bdk");
@ -103,6 +105,29 @@ impl Progress for BdkProgressHolder {
} }
} }
struct PartiallySignedBitcoinTransaction {
internal: Mutex<PartiallySignedTransaction>,
}
impl PartiallySignedBitcoinTransaction {
fn new(online_wallet: &OnlineWallet, recipient: String, amount: u64) -> Result<Self, Error> {
let wallet = online_wallet.get_wallet();
match Address::from_str(&recipient) {
Ok(address) => {
let mut builder = wallet.build_tx();
builder.add_recipient(address.script_pubkey(), amount);
let (pst, ..) = builder.finish()?;
Ok(PartiallySignedBitcoinTransaction {
internal: Mutex::new(pst),
})
}
Err(..) => Err(BdkError::AddressValidator(
AddressValidatorError::InvalidScript,
)),
}
}
}
impl OnlineWallet { impl OnlineWallet {
fn new( fn new(
descriptor: String, descriptor: String,