List both confirmed and unconfirmed transactions

This commit is contained in:
Sudarsan Balaji 2021-10-18 15:48:30 +05:30
parent c9e8368694
commit e64b1f67c1
3 changed files with 77 additions and 36 deletions

View File

@ -1,3 +1,5 @@
import java.util.Optional
import kotlin.ExperimentalUnsignedTypes
import uniffi.bdk.* import uniffi.bdk.*
class LogProgress : BdkProgress { class LogProgress : BdkProgress {
@ -10,18 +12,37 @@ class NullProgress : BdkProgress {
override fun update(progress: Float, message: String?) {} override fun update(progress: Float, message: String?) {}
} }
fun getConfirmedTransaction( fun getTransaction(wallet: OnlineWalletInterface, transactionId: String): Optional<Transaction> {
wallet: OnlineWalletInterface,
transactionId: String
): ConfirmedTransaction? {
wallet.sync(NullProgress(), null) wallet.sync(NullProgress(), null)
return wallet.getTransactions() return wallet.getTransactions()
.stream() .stream()
.filter({ it.id.equals(transactionId) }) .filter({
when (it) {
is Transaction.Confirmed -> it.details.id.equals(transactionId)
is Transaction.Unconfirmed -> it.details.id.equals(transactionId)
}
})
.findFirst() .findFirst()
.orElse(null)
} }
@ExperimentalUnsignedTypes
val unconfirmedFirstThenByTimestampDescending =
Comparator<Transaction> { a, b ->
when {
(a is Transaction.Confirmed && b is Transaction.Confirmed) -> {
val comparison = b.confirmation.timestamp.compareTo(a.confirmation.timestamp)
when {
comparison == 0 -> b.details.id.compareTo(a.details.id)
else -> comparison
}
}
(a is Transaction.Confirmed && b is Transaction.Unconfirmed) -> 1
(a is Transaction.Unconfirmed && b is Transaction.Confirmed) -> -1
else -> 0
}
}
@ExperimentalUnsignedTypes
fun main(args: Array<String>) { fun main(args: Array<String>) {
println("Configuring an in-memory wallet on electrum..") println("Configuring an in-memory wallet on electrum..")
val descriptor = "pkh(cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR)" val descriptor = "pkh(cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR)"
@ -41,23 +62,19 @@ fun main(args: Array<String>) {
println("New wallet balance: ${wallet.getBalance()}") println("New wallet balance: ${wallet.getBalance()}")
println("Press Enter to return funds") println("Press Enter to return funds")
readLine() readLine()
println( println("Creating a PSBT with recipient $recipient and amount $amount satoshis...")
"Creating a PSBT with recipient $recipient and amount $amount satoshis..."
)
val transaction = PartiallySignedBitcoinTransaction(wallet, recipient, amount) val transaction = PartiallySignedBitcoinTransaction(wallet, recipient, amount)
println("Signing the transaction...") println("Signing the transaction...")
wallet.sign(transaction) wallet.sign(transaction)
println("Broadcasting the signed transaction...") println("Broadcasting the signed transaction...")
val transactionId = wallet.broadcast(transaction) val transactionId = wallet.broadcast(transaction)
println("Broadcasted transaction with id $transactionId") println("Broadcasted transaction with id $transactionId")
println("Confirming transaction...") val take = 5
var confirmedTransaction = getConfirmedTransaction(wallet, transactionId) println("Listing latest $take transactions...")
while (confirmedTransaction == null) { wallet
confirmedTransaction = getConfirmedTransaction(wallet, transactionId) .getTransactions()
} .sortedWith(unconfirmedFirstThenByTimestampDescending)
println("Confirmed transaction: $confirmedTransaction") .take(take)
val transactions = wallet.getTransactions() .forEach { println(it) }
println("Listing all ${transactions.size} transactions...")
transactions.sortedByDescending { it.timestamp }.forEach { println(it) }
println("Final wallet balance: ${wallet.getBalance()}") println("Final wallet balance: ${wallet.getBalance()}")
} }

View File

@ -63,15 +63,24 @@ interface DatabaseConfig {
Sled(SledDbConfiguration config); Sled(SledDbConfiguration config);
}; };
dictionary ConfirmedTransaction { dictionary TransactionDetails {
u64? fees; u64? fees;
u32 height;
u64 timestamp;
u64 received; u64 received;
u64 sent; u64 sent;
string id; string id;
}; };
dictionary Confirmation {
u32 height;
u64 timestamp;
};
[Enum]
interface Transaction {
Unconfirmed(TransactionDetails details);
Confirmed(TransactionDetails details, Confirmation confirmation);
};
interface OfflineWallet { interface OfflineWallet {
[Throws=BdkError] [Throws=BdkError]
constructor(string descriptor, Network network, DatabaseConfig database_config); constructor(string descriptor, Network network, DatabaseConfig database_config);
@ -83,7 +92,7 @@ interface OfflineWallet {
[Throws=BdkError] [Throws=BdkError]
void sign([ByRef] PartiallySignedBitcoinTransaction psbt); void sign([ByRef] PartiallySignedBitcoinTransaction psbt);
[Throws=BdkError] [Throws=BdkError]
sequence<ConfirmedTransaction> get_transactions(); sequence<Transaction> get_transactions();
}; };
dictionary ElectrumConfig { dictionary ElectrumConfig {
@ -123,7 +132,7 @@ interface OnlineWallet {
[Throws=BdkError] [Throws=BdkError]
void sign([ByRef] PartiallySignedBitcoinTransaction psbt); void sign([ByRef] PartiallySignedBitcoinTransaction psbt);
[Throws=BdkError] [Throws=BdkError]
sequence<ConfirmedTransaction> get_transactions(); sequence<Transaction> get_transactions();
// OnlineWalletInterface // OnlineWalletInterface
Network get_network(); Network get_network();

View File

@ -8,7 +8,7 @@ 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, SignOptions, Wallet}; use bdk::{ConfirmationTime, Error, SignOptions, Wallet};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::str::FromStr; use std::str::FromStr;
use std::sync::{Mutex, MutexGuard}; use std::sync::{Mutex, MutexGuard};
@ -58,15 +58,24 @@ impl WalletHolder<()> for OfflineWallet {
} }
#[derive(Debug, Clone, PartialEq, Eq, Default)] #[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ConfirmedTransaction { pub struct TransactionDetails {
pub fees: Option<u64>, pub fees: Option<u64>,
pub height: u32,
pub timestamp: u64,
pub received: u64, pub received: u64,
pub sent: u64, pub sent: u64,
pub id: String, pub id: String,
} }
type Confirmation = ConfirmationTime;
pub enum Transaction {
Unconfirmed {
details: TransactionDetails,
},
Confirmed {
details: TransactionDetails,
confirmation: Confirmation,
},
}
trait OfflineWalletOperations<B>: WalletHolder<B> { trait OfflineWalletOperations<B>: WalletHolder<B> {
fn get_new_address(&self) -> String { fn get_new_address(&self) -> String {
self.get_wallet() self.get_wallet()
@ -92,18 +101,24 @@ trait OfflineWalletOperations<B>: WalletHolder<B> {
} }
} }
fn get_transactions(&self) -> Result<Vec<ConfirmedTransaction>, Error> { fn get_transactions(&self) -> Result<Vec<Transaction>, Error> {
let transactions = self.get_wallet().list_transactions(true)?; let transactions = self.get_wallet().list_transactions(true)?;
Ok(transactions Ok(transactions
.iter() .iter()
.filter(|x| x.confirmation_time.is_some()) .map(|x| -> Transaction {
.map(|x| ConfirmedTransaction { let details = TransactionDetails {
fees: x.fee, fees: x.fee,
height: x.confirmation_time.clone().map_or(0, |c| c.height), id: x.txid.to_string(),
timestamp: x.confirmation_time.clone().map_or(0, |c| c.timestamp), received: x.received,
id: x.txid.to_string(), sent: x.sent,
received: x.received, };
sent: x.sent, match x.confirmation_time.clone() {
Some(confirmation) => Transaction::Confirmed {
details,
confirmation,
},
None => Transaction::Unconfirmed { details },
}
}) })
.collect()) .collect())
} }