diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index 53c0944..0f34488 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -584,6 +584,8 @@ interface Transaction { u64 weight(); sequence input(); + + sequence output(); }; interface Psbt { diff --git a/bdk-ffi/src/bitcoin.rs b/bdk-ffi/src/bitcoin.rs index 240fcda..b9b10be 100644 --- a/bdk-ffi/src/bitcoin.rs +++ b/bdk-ffi/src/bitcoin.rs @@ -1,27 +1,26 @@ use crate::error::{AddressError, FeeRateError, PsbtParseError, TransactionError}; use bdk::bitcoin::address::{NetworkChecked, NetworkUnchecked}; +use bdk::bitcoin::amount::ParseAmountError; use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf; use bdk::bitcoin::blockdata::transaction::TxOut as BdkTxOut; use bdk::bitcoin::consensus::encode::serialize; use bdk::bitcoin::consensus::Decodable; use bdk::bitcoin::psbt::ExtractTxError; use bdk::bitcoin::Address as BdkAddress; +use bdk::bitcoin::Amount as BdkAmount; use bdk::bitcoin::FeeRate as BdkFeeRate; use bdk::bitcoin::Network; use bdk::bitcoin::OutPoint as BdkOutPoint; use bdk::bitcoin::Psbt as BdkPsbt; use bdk::bitcoin::Transaction as BdkTransaction; -use bdk::bitcoin::Txid; use bdk::bitcoin::TxIn as BdkTxIn; -use bdk::bitcoin::amount::ParseAmountError; -use bdk::bitcoin::Amount as BdkAmount; +use bdk::bitcoin::Txid; use std::io::Cursor; use std::str::FromStr; use std::sync::{Arc, Mutex}; - #[derive(Clone, Debug, PartialEq, Eq)] pub struct Amount(pub(crate) BdkAmount); @@ -174,6 +173,10 @@ impl Transaction { pub fn input(&self) -> Vec { self.0.input.iter().map(|tx_in| tx_in.into()).collect() } + + pub fn output(&self) -> Vec { + self.0.output.iter().map(|tx_out| tx_out.into()).collect() + } } impl From for Transaction { diff --git a/bdk-ffi/src/lib.rs b/bdk-ffi/src/lib.rs index 3462696..4e23353 100644 --- a/bdk-ffi/src/lib.rs +++ b/bdk-ffi/src/lib.rs @@ -14,6 +14,7 @@ use crate::bitcoin::OutPoint; use crate::bitcoin::Psbt; use crate::bitcoin::Script; use crate::bitcoin::Transaction; +use crate::bitcoin::TxIn; use crate::bitcoin::TxOut; use crate::descriptor::Descriptor; use crate::electrum::ElectrumClient; @@ -53,7 +54,6 @@ use crate::wallet::BumpFeeTxBuilder; use crate::wallet::SentAndReceivedValues; use crate::wallet::TxBuilder; use crate::wallet::Update; -use crate::bitcoin::TxIn; use crate::wallet::Wallet; use bdk::bitcoin::Network; diff --git a/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/LiveTransactionTests.kt b/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/LiveTransactionTests.kt new file mode 100644 index 0000000..0321744 --- /dev/null +++ b/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/LiveTransactionTests.kt @@ -0,0 +1,39 @@ +package org.bitcoindevkit + +import kotlin.test.Test + +private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net" +private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud" + +class LiveTransactionTests { + @Test + fun testSyncedBalance() { + val descriptor: Descriptor = Descriptor( + "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", + Network.SIGNET + ) + val wallet: Wallet = Wallet.newNoPersist(descriptor, null, Network.SIGNET) + val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL) + val fullScanRequest: FullScanRequest = wallet.startFullScan() + val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL) + wallet.applyUpdate(update) + wallet.commit() + println("Wallet balance: ${wallet.getBalance().total.toSat()}") + + assert(wallet.getBalance().total.toSat() > 0uL) { + "Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} and try again." + } + + val transaction: Transaction = wallet.transactions().first().transaction + println("First transaction:") + println("Txid: ${transaction.txid()}") + println("Version: ${transaction.version()}") + println("Total size: ${transaction.totalSize()}") + println("Vsize: ${transaction.vsize()}") + println("Weight: ${transaction.weight()}") + println("Coinbase transaction: ${transaction.isCoinbase()}") + println("Is explicitly RBF: ${transaction.isExplicitlyRbf()}") + println("Inputs: ${transaction.input()}") + println("Outputs: ${transaction.output()}") + } +} diff --git a/bdk-swift/Tests/BitcoinDevKitTests/LiveTransactionTests.swift b/bdk-swift/Tests/BitcoinDevKitTests/LiveTransactionTests.swift new file mode 100644 index 0000000..da82fd3 --- /dev/null +++ b/bdk-swift/Tests/BitcoinDevKitTests/LiveTransactionTests.swift @@ -0,0 +1,51 @@ +import XCTest +@testable import BitcoinDevKit + +private let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net" +private let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud" + +final class LiveTransactionTests: XCTestCase { + func testSyncedBalance() throws { + let descriptor = try Descriptor( + descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", + network: Network.signet + ) + let wallet = try Wallet.newNoPersist( + descriptor: descriptor, + changeDescriptor: nil, + network: .signet + ) + let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL) + let fullScanRequest: FullScanRequest = wallet.startFullScan() + let update = try esploraClient.fullScan( + fullScanRequest: fullScanRequest, + stopGap: 10, + parallelRequests: 1 + ) + try wallet.applyUpdate(update: update) + let _ = try wallet.commit() + let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString() + + XCTAssertGreaterThan( + wallet.getBalance().total.toSat(), + UInt64(0), + "Wallet must have positive balance, please send funds to \(address)" + ) + + guard let transaction = wallet.transactions().first?.transaction else { + print("No transactions found") + return + } + print("First transaction:") + print("Txid: \(transaction.txid())") + print("Version: \(transaction.version())") + print("Total size: \(transaction.totalSize())") + print("Vsize: \(transaction.vsize())") + print("Weight: \(transaction.weight())") + print("Coinbase transaction: \(transaction.isCoinbase())") + print("Is explicitly RBF: \(transaction.isExplicitlyRbf())") + print("Inputs: \(transaction.input())") + print("Outputs: \(transaction.output())") + + } +}