diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index 3254ab8..3b9a056 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -102,7 +102,7 @@ interface DescriptorKeyError { }; [Error] -interface ElectrumClientError { +interface ElectrumError { IOError(string error_message); Json(string error_message); Hex(string error_message); @@ -493,16 +493,16 @@ interface EsploraClient { // ------------------------------------------------------------------------ interface ElectrumClient { - [Throws=ElectrumClientError] + [Throws=ElectrumError] constructor(string url); - [Throws=ElectrumClientError] + [Throws=ElectrumError] Update full_scan(FullScanRequest full_scan_request, u64 stop_gap, u64 batch_size, boolean fetch_prev_txouts); - [Throws=ElectrumClientError] + [Throws=ElectrumError] Update sync(SyncRequest sync_request, u64 batch_size, boolean fetch_prev_txouts); - [Throws=ElectrumClientError] + [Throws=ElectrumError] string broadcast([ByRef] Transaction transaction); }; diff --git a/bdk-ffi/src/electrum.rs b/bdk-ffi/src/electrum.rs index bbc5d68..e0d1620 100644 --- a/bdk-ffi/src/electrum.rs +++ b/bdk-ffi/src/electrum.rs @@ -1,7 +1,7 @@ -use crate::error::ElectrumClientError; +use crate::bitcoin::Transaction; +use crate::error::ElectrumError; use crate::types::{FullScanRequest, SyncRequest}; use crate::wallet::Update; -use std::collections::BTreeMap; use bdk::bitcoin::Transaction as BdkTransaction; use bdk::chain::spk_client::FullScanRequest as BdkFullScanRequest; @@ -12,13 +12,13 @@ use bdk::KeychainKind; use bdk_electrum::electrum_client::{Client as BdkBlockingClient, ElectrumApi}; use bdk_electrum::{ElectrumExt, ElectrumFullScanResult, ElectrumSyncResult}; -use crate::bitcoin::Transaction; +use std::collections::BTreeMap; use std::sync::Arc; pub struct ElectrumClient(BdkBlockingClient); impl ElectrumClient { - pub fn new(url: String) -> Result { + pub fn new(url: String) -> Result { let client = BdkBlockingClient::new(url.as_str())?; Ok(Self(client)) } @@ -29,14 +29,14 @@ impl ElectrumClient { stop_gap: u64, batch_size: u64, fetch_prev_txouts: bool, - ) -> Result, ElectrumClientError> { + ) -> Result, ElectrumError> { // using option and take is not ideal but the only way to take full ownership of the request let request: BdkFullScanRequest = request .0 .lock() .unwrap() .take() - .ok_or(ElectrumClientError::RequestAlreadyConsumed)?; + .ok_or(ElectrumError::RequestAlreadyConsumed)?; let electrum_result: ElectrumFullScanResult = self.0.full_scan( request, @@ -61,14 +61,14 @@ impl ElectrumClient { request: Arc, batch_size: u64, fetch_prev_txouts: bool, - ) -> Result, ElectrumClientError> { + ) -> Result, ElectrumError> { // using option and take is not ideal but the only way to take full ownership of the request let request: BdkSyncRequest = request .0 .lock() .unwrap() .take() - .ok_or(ElectrumClientError::RequestAlreadyConsumed)?; + .ok_or(ElectrumError::RequestAlreadyConsumed)?; let electrum_result: ElectrumSyncResult = self.0 @@ -85,11 +85,11 @@ impl ElectrumClient { Ok(Arc::new(Update(update))) } - pub fn broadcast(&self, transaction: &Transaction) -> Result { + pub fn broadcast(&self, transaction: &Transaction) -> Result { let bdk_transaction: BdkTransaction = transaction.into(); self.0 .transaction_broadcast(&bdk_transaction) - .map_err(ElectrumClientError::from) + .map_err(ElectrumError::from) .map(|txid| txid.to_string()) } } diff --git a/bdk-ffi/src/error.rs b/bdk-ffi/src/error.rs index 3b62316..2c91d6a 100644 --- a/bdk-ffi/src/error.rs +++ b/bdk-ffi/src/error.rs @@ -257,7 +257,7 @@ pub enum DescriptorKeyError { } #[derive(Debug, thiserror::Error)] -pub enum ElectrumClientError { +pub enum ElectrumError { #[error("{error_message}")] IOError { error_message: String }, @@ -558,44 +558,44 @@ impl From for AddressError { } } -impl From for ElectrumClientError { +impl From for ElectrumError { fn from(error: BdkElectrumError) -> Self { match error { - BdkElectrumError::IOError(e) => ElectrumClientError::IOError { + BdkElectrumError::IOError(e) => ElectrumError::IOError { error_message: e.to_string(), }, - BdkElectrumError::JSON(e) => ElectrumClientError::Json { + BdkElectrumError::JSON(e) => ElectrumError::Json { error_message: e.to_string(), }, - BdkElectrumError::Hex(e) => ElectrumClientError::Hex { + BdkElectrumError::Hex(e) => ElectrumError::Hex { error_message: e.to_string(), }, - BdkElectrumError::Protocol(e) => ElectrumClientError::Protocol { + BdkElectrumError::Protocol(e) => ElectrumError::Protocol { error_message: e.to_string(), }, - BdkElectrumError::Bitcoin(e) => ElectrumClientError::Bitcoin { + BdkElectrumError::Bitcoin(e) => ElectrumError::Bitcoin { error_message: e.to_string(), }, - BdkElectrumError::AlreadySubscribed(_) => ElectrumClientError::AlreadySubscribed, - BdkElectrumError::NotSubscribed(_) => ElectrumClientError::NotSubscribed, - BdkElectrumError::InvalidResponse(e) => ElectrumClientError::InvalidResponse { + BdkElectrumError::AlreadySubscribed(_) => ElectrumError::AlreadySubscribed, + BdkElectrumError::NotSubscribed(_) => ElectrumError::NotSubscribed, + BdkElectrumError::InvalidResponse(e) => ElectrumError::InvalidResponse { error_message: e.to_string(), }, - BdkElectrumError::Message(e) => ElectrumClientError::Message { + BdkElectrumError::Message(e) => ElectrumError::Message { error_message: e.to_string(), }, BdkElectrumError::InvalidDNSNameError(domain) => { - ElectrumClientError::InvalidDNSNameError { domain } + ElectrumError::InvalidDNSNameError { domain } } - BdkElectrumError::MissingDomain => ElectrumClientError::MissingDomain, - BdkElectrumError::AllAttemptsErrored(_) => ElectrumClientError::AllAttemptsErrored, - BdkElectrumError::SharedIOError(e) => ElectrumClientError::SharedIOError { + BdkElectrumError::MissingDomain => ElectrumError::MissingDomain, + BdkElectrumError::AllAttemptsErrored(_) => ElectrumError::AllAttemptsErrored, + BdkElectrumError::SharedIOError(e) => ElectrumError::SharedIOError { error_message: e.to_string(), }, - BdkElectrumError::CouldntLockReader => ElectrumClientError::CouldntLockReader, - BdkElectrumError::Mpsc => ElectrumClientError::Mpsc, + BdkElectrumError::CouldntLockReader => ElectrumError::CouldntLockReader, + BdkElectrumError::Mpsc => ElectrumError::Mpsc, BdkElectrumError::CouldNotCreateConnection(error_message) => { - ElectrumClientError::CouldNotCreateConnection { + ElectrumError::CouldNotCreateConnection { error_message: error_message.to_string(), } } @@ -1079,7 +1079,7 @@ impl From for WalletCreationError { mod test { use crate::error::{ AddressError, Bip32Error, Bip39Error, CannotConnectError, CreateTxError, DescriptorError, - DescriptorKeyError, ElectrumClientError, EsploraError, ExtractTxError, FeeRateError, + DescriptorKeyError, ElectrumError, EsploraError, ExtractTxError, FeeRateError, ParseAmountError, PersistenceError, PsbtParseError, TransactionError, TxidParseError, WalletCreationError, }; @@ -1488,77 +1488,77 @@ mod test { fn test_error_electrum_client() { let cases = vec![ ( - ElectrumClientError::IOError { error_message: "message".to_string(), }, + ElectrumError::IOError { error_message: "message".to_string(), }, "message", ), ( - ElectrumClientError::Json { error_message: "message".to_string(), }, + ElectrumError::Json { error_message: "message".to_string(), }, "message", ), ( - ElectrumClientError::Hex { error_message: "message".to_string(), }, + ElectrumError::Hex { error_message: "message".to_string(), }, "message", ), ( - ElectrumClientError::Protocol { error_message: "message".to_string(), }, + ElectrumError::Protocol { error_message: "message".to_string(), }, "electrum server error: message", ), ( - ElectrumClientError::Bitcoin { + ElectrumError::Bitcoin { error_message: "message".to_string(), }, "message", ), ( - ElectrumClientError::AlreadySubscribed, + ElectrumError::AlreadySubscribed, "already subscribed to the notifications of an address", ), ( - ElectrumClientError::NotSubscribed, + ElectrumError::NotSubscribed, "not subscribed to the notifications of an address", ), ( - ElectrumClientError::InvalidResponse { + ElectrumError::InvalidResponse { error_message: "message".to_string(), }, "error during the deserialization of a response from the server: message", ), ( - ElectrumClientError::Message { + ElectrumError::Message { error_message: "message".to_string(), }, "message", ), ( - ElectrumClientError::InvalidDNSNameError { + ElectrumError::InvalidDNSNameError { domain: "domain".to_string(), }, "invalid domain name domain not matching SSL certificate", ), ( - ElectrumClientError::MissingDomain, + ElectrumError::MissingDomain, "missing domain while it was explicitly asked to validate it", ), ( - ElectrumClientError::AllAttemptsErrored, + ElectrumError::AllAttemptsErrored, "made one or multiple attempts, all errored", ), ( - ElectrumClientError::SharedIOError { + ElectrumError::SharedIOError { error_message: "message".to_string(), }, "message", ), ( - ElectrumClientError::CouldntLockReader, + ElectrumError::CouldntLockReader, "couldn't take a lock on the reader mutex. This means that there's already another reader thread is running" ), ( - ElectrumClientError::Mpsc, + ElectrumError::Mpsc, "broken IPC communication channel: the other thread probably has exited", ), ( - ElectrumClientError::CouldNotCreateConnection { + ElectrumError::CouldNotCreateConnection { error_message: "message".to_string(), }, "message", diff --git a/bdk-ffi/src/lib.rs b/bdk-ffi/src/lib.rs index 280d48a..e9f1ebf 100644 --- a/bdk-ffi/src/lib.rs +++ b/bdk-ffi/src/lib.rs @@ -25,7 +25,7 @@ use crate::error::CannotConnectError; use crate::error::CreateTxError; use crate::error::DescriptorError; use crate::error::DescriptorKeyError; -use crate::error::ElectrumClientError; +use crate::error::ElectrumError; use crate::error::EsploraError; use crate::error::ExtractTxError; use crate::error::FeeRateError; diff --git a/bdk-swift/Tests/BitcoinDevKitTests/LiveElectrumClientTests.swift b/bdk-swift/Tests/BitcoinDevKitTests/LiveElectrumClientTests.swift new file mode 100644 index 0000000..784ab3e --- /dev/null +++ b/bdk-swift/Tests/BitcoinDevKitTests/LiveElectrumClientTests.swift @@ -0,0 +1,44 @@ +import XCTest +@testable import BitcoinDevKit + +private let SIGNET_ELECTRUM_URL = "ssl://mempool.space:60602" + +final class LiveElectrumClientTests: 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 electrumClient: ElectrumClient = try ElectrumClient(url: SIGNET_ELECTRUM_URL) + let fullScanRequest: FullScanRequest = wallet.startFullScan() + let update = try electrumClient.fullScan( + fullScanRequest: fullScanRequest, + stopGap: 10, + batchSize: 10, + fetchPrevTxouts: false + ) + 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)" + ) + + print("Transactions count: \(wallet.transactions().count)") + let transactions = wallet.transactions().prefix(3) + for tx in transactions { + let sentAndReceived = wallet.sentAndReceived(tx: tx.transaction) + print("Transaction: \(tx.transaction.txid())") + print("Sent \(sentAndReceived.sent)") + print("Received \(sentAndReceived.received)") + } + } +}