Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add simple electrum client #535

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions bdk-ffi/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bdk-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ default = ["uniffi/cli"]
[dependencies]
bdk = { version = "1.0.0-alpha.11", features = ["all-keys", "keys-bip39"] }
bdk_esplora = { version = "0.13.0", default-features = false, features = ["std", "blocking", "blocking-https-rustls"] }
bdk_electrum = { version = "0.13.0" }
bdk_file_store = { version = "0.11.0" }

uniffi = { version = "=0.26.1" }
Expand Down
42 changes: 42 additions & 0 deletions bdk-ffi/src/bdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,27 @@ interface DescriptorKeyError {
Bip32(string error_message);
};

[Error]
interface ElectrumError {
IOError(string error_message);
Json(string error_message);
Hex(string error_message);
Protocol(string error_message);
Bitcoin(string error_message);
AlreadySubscribed();
NotSubscribed();
InvalidResponse(string error_message);
Message(string error_message);
InvalidDNSNameError(string domain);
MissingDomain();
AllAttemptsErrored();
SharedIOError(string error_message);
CouldntLockReader();
Mpsc();
CouldNotCreateConnection(string error_message);
RequestAlreadyConsumed();
};

[Error]
interface EsploraError {
Minreq(string error_message);
Expand Down Expand Up @@ -271,6 +292,9 @@ interface Wallet {
[Throws=WalletCreationError]
constructor(Descriptor descriptor, Descriptor? change_descriptor, string persistence_backend_path, Network network);

[Name=new_no_persist, Throws=DescriptorError]
constructor(Descriptor descriptor, Descriptor? change_descriptor, Network network);

[Throws=PersistenceError]
AddressInfo reveal_next_address(KeychainKind keychain);

Expand Down Expand Up @@ -464,6 +488,24 @@ interface EsploraClient {
void broadcast([ByRef] Transaction transaction);
};

// ------------------------------------------------------------------------
// bdk_electrum crate
// ------------------------------------------------------------------------

interface ElectrumClient {
[Throws=ElectrumError]
constructor(string url);

[Throws=ElectrumError]
Update full_scan(FullScanRequest full_scan_request, u64 stop_gap, u64 batch_size, boolean fetch_prev_txouts);

[Throws=ElectrumError]
Update sync(SyncRequest sync_request, u64 batch_size, boolean fetch_prev_txouts);

[Throws=ElectrumError]
string broadcast([ByRef] Transaction transaction);
};

// ------------------------------------------------------------------------
// bdk-ffi-defined types
// ------------------------------------------------------------------------
Expand Down
95 changes: 95 additions & 0 deletions bdk-ffi/src/electrum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use crate::bitcoin::Transaction;
use crate::error::ElectrumError;
use crate::types::{FullScanRequest, SyncRequest};
use crate::wallet::Update;

use bdk::bitcoin::Transaction as BdkTransaction;
use bdk::chain::spk_client::FullScanRequest as BdkFullScanRequest;
use bdk::chain::spk_client::FullScanResult as BdkFullScanResult;
use bdk::chain::spk_client::SyncRequest as BdkSyncRequest;
use bdk::chain::spk_client::SyncResult as BdkSyncResult;
use bdk::KeychainKind;
use bdk_electrum::electrum_client::{Client as BdkBlockingClient, ElectrumApi};
use bdk_electrum::{ElectrumExt, ElectrumFullScanResult, ElectrumSyncResult};

use std::collections::BTreeMap;
use std::sync::Arc;

pub struct ElectrumClient(BdkBlockingClient);

impl ElectrumClient {
pub fn new(url: String) -> Result<Self, ElectrumError> {
let client = BdkBlockingClient::new(url.as_str())?;
Ok(Self(client))
}

pub fn full_scan(
&self,
request: Arc<FullScanRequest>,
stop_gap: u64,
batch_size: u64,
fetch_prev_txouts: bool,
) -> Result<Arc<Update>, ElectrumError> {
// using option and take is not ideal but the only way to take full ownership of the request
let request: BdkFullScanRequest<KeychainKind> = request
.0
.lock()
.unwrap()
.take()
.ok_or(ElectrumError::RequestAlreadyConsumed)?;

let electrum_result: ElectrumFullScanResult<KeychainKind> = self.0.full_scan(
request,
stop_gap as usize,
batch_size as usize,
fetch_prev_txouts,
)?;
let full_scan_result: BdkFullScanResult<KeychainKind> =
electrum_result.with_confirmation_time_height_anchor(&self.0)?;

let update = bdk::wallet::Update {
last_active_indices: full_scan_result.last_active_indices,
graph: full_scan_result.graph_update,
chain: Some(full_scan_result.chain_update),
};

Ok(Arc::new(Update(update)))
}

pub fn sync(
&self,
request: Arc<SyncRequest>,
batch_size: u64,
fetch_prev_txouts: bool,
) -> Result<Arc<Update>, 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(ElectrumError::RequestAlreadyConsumed)?;

let electrum_result: ElectrumSyncResult =
self.0
.sync(request, batch_size as usize, fetch_prev_txouts)?;
let sync_result: BdkSyncResult =
electrum_result.with_confirmation_time_height_anchor(&self.0)?;

let update = bdk::wallet::Update {
last_active_indices: BTreeMap::default(),
graph: sync_result.graph_update,
chain: Some(sync_result.chain_update),
};

Ok(Arc::new(Update(update)))
}

pub fn broadcast(&self, transaction: &Transaction) -> Result<String, ElectrumError> {
let bdk_transaction: BdkTransaction = transaction.into();
self.0
.transaction_broadcast(&bdk_transaction)
.map_err(ElectrumError::from)
.map(|txid| txid.to_string())
}
}
Loading
Loading