feat: add sync and full_scan methods on esplora client
This commit is contained in:
parent
19723240b7
commit
72b5bfd4c9
@ -26,7 +26,8 @@ class LiveTxBuilderTest {
|
||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||
val wallet = Wallet(descriptor, null, persistenceFilePath, Network.TESTNET)
|
||||
val esploraClient = EsploraClient("https://esplora.testnet.kuutamo.cloud/")
|
||||
val update = esploraClient.fullScan(wallet, 10uL, 1uL)
|
||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||
wallet.applyUpdate(update)
|
||||
println("Balance: ${wallet.getBalance().total}")
|
||||
|
||||
@ -48,7 +49,8 @@ class LiveTxBuilderTest {
|
||||
val changeDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.TESTNET)
|
||||
val wallet = Wallet(externalDescriptor, changeDescriptor, persistenceFilePath, Network.TESTNET)
|
||||
val esploraClient = EsploraClient("https://esplora.testnet.kuutamo.cloud/")
|
||||
val update = esploraClient.fullScan(wallet, 10uL, 1uL)
|
||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||
wallet.applyUpdate(update)
|
||||
println("Balance: ${wallet.getBalance().total}")
|
||||
|
||||
|
@ -26,7 +26,8 @@ class LiveWalletTest {
|
||||
val descriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||
val wallet: Wallet = Wallet(descriptor, null, persistenceFilePath, Network.TESTNET)
|
||||
val esploraClient: EsploraClient = EsploraClient("https://esplora.testnet.kuutamo.cloud/")
|
||||
val update = esploraClient.fullScan(wallet, 10uL, 1uL)
|
||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||
wallet.applyUpdate(update)
|
||||
println("Balance: ${wallet.getBalance().total}")
|
||||
val balance: Balance = wallet.getBalance()
|
||||
@ -49,14 +50,15 @@ class LiveWalletTest {
|
||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||
val wallet = Wallet(descriptor, null, persistenceFilePath, Network.TESTNET)
|
||||
val esploraClient = EsploraClient("https://esplora.testnet.kuutamo.cloud/")
|
||||
val update = esploraClient.fullScan(wallet, 10uL, 1uL)
|
||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||
|
||||
wallet.applyUpdate(update)
|
||||
println("Balance: ${wallet.getBalance().total}")
|
||||
println("New address: ${wallet.getAddress(AddressIndex.New).address}")
|
||||
println("New address: ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address}")
|
||||
|
||||
assert(wallet.getBalance().total > 0uL) {
|
||||
"Wallet balance must be greater than 0! Please send funds to ${wallet.getAddress(AddressIndex.New).address} and try again."
|
||||
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
|
||||
}
|
||||
|
||||
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
|
||||
|
@ -44,7 +44,7 @@ class OfflineWalletTest {
|
||||
persistenceFilePath,
|
||||
Network.TESTNET
|
||||
)
|
||||
val addressInfo: AddressInfo = wallet.getAddress(AddressIndex.New)
|
||||
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
|
||||
|
||||
assertTrue(addressInfo.address.isValidForNetwork(Network.TESTNET), "Address is not valid for testnet network")
|
||||
assertTrue(addressInfo.address.isValidForNetwork(Network.SIGNET), "Address is not valid for signet network")
|
||||
|
@ -115,6 +115,7 @@ interface EsploraError {
|
||||
HeaderHashNotFound();
|
||||
InvalidHttpHeaderName(string name);
|
||||
InvalidHttpHeaderValue(string value);
|
||||
RequestAlreadyConsumed();
|
||||
};
|
||||
|
||||
[Error]
|
||||
@ -238,6 +239,10 @@ dictionary CanonicalTx {
|
||||
ChainPosition chain_position;
|
||||
};
|
||||
|
||||
interface FullScanRequest {};
|
||||
|
||||
interface SyncRequest {};
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// bdk crate - wallet module
|
||||
// ------------------------------------------------------------------------
|
||||
@ -283,6 +288,10 @@ interface Wallet {
|
||||
sequence<LocalOutput> list_unspent();
|
||||
|
||||
sequence<LocalOutput> list_output();
|
||||
|
||||
FullScanRequest start_full_scan();
|
||||
|
||||
SyncRequest start_sync_with_revealed_spks();
|
||||
};
|
||||
|
||||
interface Update {};
|
||||
@ -429,7 +438,10 @@ interface EsploraClient {
|
||||
constructor(string url);
|
||||
|
||||
[Throws=EsploraError]
|
||||
Update full_scan(Wallet wallet, u64 stop_gap, u64 parallel_requests);
|
||||
Update full_scan(FullScanRequest full_scan_request, u64 stop_gap, u64 parallel_requests);
|
||||
|
||||
[Throws=EsploraError]
|
||||
Update sync(SyncRequest sync_request, u64 parallel_requests);
|
||||
|
||||
[Throws=EsploraError]
|
||||
void broadcast([ByRef] Transaction transaction);
|
||||
|
@ -1,34 +1,28 @@
|
||||
use crate::bitcoin::OutPoint;
|
||||
|
||||
use bdk::bitcoin::address::ParseError;
|
||||
use bdk::bitcoin::bip32::Error as BdkBip32Error;
|
||||
use bdk::bitcoin::psbt::PsbtParseError as BdkPsbtParseError;
|
||||
use bdk::bitcoin::Network;
|
||||
use bdk::chain::tx_graph::CalculateFeeError as BdkCalculateFeeError;
|
||||
use bdk::descriptor::DescriptorError as BdkDescriptorError;
|
||||
use bdk::keys::bip39::Error as BdkBip39Error;
|
||||
use bdk::miniscript::descriptor::DescriptorKeyParseError as BdkDescriptorKeyParseError;
|
||||
use bdk::wallet::error::BuildFeeBumpError;
|
||||
use bdk::wallet::error::CreateTxError as BdkCreateTxError;
|
||||
use bdk::wallet::signer::SignerError as BdkSignerError;
|
||||
use bdk::wallet::tx_builder::AddUtxoError;
|
||||
use bdk::wallet::NewOrLoadError;
|
||||
use bdk_esplora::esplora_client::{Error as BdkEsploraError, Error};
|
||||
use bdk_file_store::FileError as BdkFileError;
|
||||
use bdk_file_store::IterError;
|
||||
use bitcoin_internals::hex::display::DisplayHex;
|
||||
|
||||
use crate::error::bip32::Error as BdkBip32Error;
|
||||
use bdk::bitcoin::address::ParseError;
|
||||
use bdk::keys::bip39::Error as BdkBip39Error;
|
||||
use bdk::miniscript::descriptor::DescriptorKeyParseError as BdkDescriptorKeyParseError;
|
||||
|
||||
use bdk::bitcoin::bip32;
|
||||
|
||||
use bdk::chain;
|
||||
|
||||
use bdk::wallet::error::CreateTxError as BdkCreateTxError;
|
||||
use std::convert::TryInto;
|
||||
|
||||
use bdk::bitcoin::address::Error as BdkAddressError;
|
||||
use bdk::bitcoin::consensus::encode::Error as BdkEncodeError;
|
||||
use bdk::bitcoin::psbt::ExtractTxError as BdkExtractTxError;
|
||||
use chain::local_chain::CannotConnectError as BdkCannotConnectError;
|
||||
use bdk::chain::local_chain::CannotConnectError as BdkCannotConnectError;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// error definitions
|
||||
@ -295,6 +289,9 @@ pub enum EsploraError {
|
||||
|
||||
#[error("invalid HTTP header value: {value}")]
|
||||
InvalidHttpHeaderValue { value: String },
|
||||
|
||||
#[error("the request has already been consumed")]
|
||||
RequestAlreadyConsumed,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@ -411,6 +408,7 @@ pub enum TxidParseError {
|
||||
InvalidTxid { txid: String },
|
||||
}
|
||||
|
||||
// This error combines the Rust bdk::wallet::NewOrLoadError and bdk_file_store::FileError
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum WalletCreationError {
|
||||
#[error("io error trying to read file: {error_message}")]
|
||||
@ -422,17 +420,14 @@ pub enum WalletCreationError {
|
||||
#[error("error with descriptor")]
|
||||
Descriptor,
|
||||
|
||||
#[error("failed to write to persistence")]
|
||||
Write,
|
||||
|
||||
#[error("failed to load from persistence")]
|
||||
Load,
|
||||
#[error("failed to either write to or load from persistence, {error_message}")]
|
||||
Persist { error_message: String },
|
||||
|
||||
#[error("wallet is not initialized, persistence backend is empty")]
|
||||
NotInitialized,
|
||||
|
||||
#[error("loaded genesis hash does not match the expected one")]
|
||||
LoadedGenesisDoesNotMatch,
|
||||
#[error("loaded genesis hash '{got}' does not match the expected one '{expected}'")]
|
||||
LoadedGenesisDoesNotMatch { expected: String, got: String },
|
||||
|
||||
#[error("loaded network type is not {expected}, got {got:?}")]
|
||||
LoadedNetworkDoesNotMatch {
|
||||
@ -560,8 +555,8 @@ impl From<BdkCannotConnectError> for CannotConnectError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BdkCreateTxError<std::io::Error>> for CreateTxError {
|
||||
fn from(error: BdkCreateTxError<std::io::Error>) -> Self {
|
||||
impl From<BdkCreateTxError> for CreateTxError {
|
||||
fn from(error: BdkCreateTxError) -> Self {
|
||||
match error {
|
||||
BdkCreateTxError::Descriptor(e) => CreateTxError::Descriptor {
|
||||
error_message: e.to_string(),
|
||||
@ -751,6 +746,44 @@ impl From<BdkEsploraError> for EsploraError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<BdkEsploraError>> for EsploraError {
|
||||
fn from(error: Box<BdkEsploraError>) -> Self {
|
||||
match *error {
|
||||
BdkEsploraError::Minreq(e) => EsploraError::Minreq {
|
||||
error_message: e.to_string(),
|
||||
},
|
||||
BdkEsploraError::HttpResponse { status, message } => EsploraError::HttpResponse {
|
||||
status,
|
||||
error_message: message,
|
||||
},
|
||||
BdkEsploraError::Parsing(e) => EsploraError::Parsing {
|
||||
error_message: e.to_string(),
|
||||
},
|
||||
Error::StatusCode(e) => EsploraError::StatusCode {
|
||||
error_message: e.to_string(),
|
||||
},
|
||||
BdkEsploraError::BitcoinEncoding(e) => EsploraError::BitcoinEncoding {
|
||||
error_message: e.to_string(),
|
||||
},
|
||||
BdkEsploraError::HexToArray(e) => EsploraError::HexToArray {
|
||||
error_message: e.to_string(),
|
||||
},
|
||||
BdkEsploraError::HexToBytes(e) => EsploraError::HexToBytes {
|
||||
error_message: e.to_string(),
|
||||
},
|
||||
BdkEsploraError::TransactionNotFound(_) => EsploraError::TransactionNotFound,
|
||||
BdkEsploraError::HeaderHeightNotFound(height) => {
|
||||
EsploraError::HeaderHeightNotFound { height }
|
||||
}
|
||||
BdkEsploraError::HeaderHashNotFound(_) => EsploraError::HeaderHashNotFound,
|
||||
Error::InvalidHttpHeaderName(name) => EsploraError::InvalidHttpHeaderName { name },
|
||||
BdkEsploraError::InvalidHttpHeaderValue(value) => {
|
||||
EsploraError::InvalidHttpHeaderValue { value }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BdkExtractTxError> for ExtractTxError {
|
||||
fn from(error: BdkExtractTxError) -> Self {
|
||||
match error {
|
||||
@ -852,15 +885,19 @@ impl From<BdkFileError> for WalletCreationError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NewOrLoadError<std::io::Error, IterError>> for WalletCreationError {
|
||||
fn from(error: NewOrLoadError<std::io::Error, IterError>) -> Self {
|
||||
impl From<NewOrLoadError> for WalletCreationError {
|
||||
fn from(error: NewOrLoadError) -> Self {
|
||||
match error {
|
||||
NewOrLoadError::Descriptor(_) => WalletCreationError::Descriptor,
|
||||
NewOrLoadError::Write(_) => WalletCreationError::Write,
|
||||
NewOrLoadError::Load(_) => WalletCreationError::Load,
|
||||
NewOrLoadError::Persist(e) => WalletCreationError::Persist {
|
||||
error_message: e.to_string(),
|
||||
},
|
||||
NewOrLoadError::NotInitialized => WalletCreationError::NotInitialized,
|
||||
NewOrLoadError::LoadedGenesisDoesNotMatch { .. } => {
|
||||
WalletCreationError::LoadedGenesisDoesNotMatch
|
||||
NewOrLoadError::LoadedGenesisDoesNotMatch { expected, got } => {
|
||||
WalletCreationError::LoadedGenesisDoesNotMatch {
|
||||
expected: expected.to_string(),
|
||||
got: format!("{:?}", got),
|
||||
}
|
||||
}
|
||||
NewOrLoadError::LoadedNetworkDoesNotMatch { expected, got } => {
|
||||
WalletCreationError::LoadedNetworkDoesNotMatch { expected, got }
|
||||
@ -1332,6 +1369,10 @@ mod test {
|
||||
"header height 123456 not found",
|
||||
),
|
||||
(EsploraError::HeaderHashNotFound, "header hash not found"),
|
||||
(
|
||||
EsploraError::RequestAlreadyConsumed,
|
||||
"the request has already been consumed",
|
||||
),
|
||||
];
|
||||
|
||||
for (error, expected_message) in cases {
|
||||
@ -1541,20 +1582,21 @@ mod test {
|
||||
"error with descriptor".to_string(),
|
||||
),
|
||||
(
|
||||
WalletCreationError::Write,
|
||||
"failed to write to persistence".to_string(),
|
||||
),
|
||||
(
|
||||
WalletCreationError::Load,
|
||||
"failed to load from persistence".to_string(),
|
||||
WalletCreationError::Persist {
|
||||
error_message: "persistence error".to_string(),
|
||||
},
|
||||
"failed to either write to or load from persistence, persistence error".to_string(),
|
||||
),
|
||||
(
|
||||
WalletCreationError::NotInitialized,
|
||||
"wallet is not initialized, persistence backend is empty".to_string(),
|
||||
),
|
||||
(
|
||||
WalletCreationError::LoadedGenesisDoesNotMatch,
|
||||
"loaded genesis hash does not match the expected one".to_string(),
|
||||
WalletCreationError::LoadedGenesisDoesNotMatch {
|
||||
expected: "abc".to_string(),
|
||||
got: "def".to_string(),
|
||||
},
|
||||
"loaded genesis hash 'def' does not match the expected one 'abc'".to_string(),
|
||||
),
|
||||
(
|
||||
WalletCreationError::LoadedNetworkDoesNotMatch {
|
||||
|
@ -1,9 +1,15 @@
|
||||
use crate::error::EsploraError;
|
||||
use crate::wallet::{Update, Wallet};
|
||||
|
||||
use crate::bitcoin::Transaction;
|
||||
use crate::error::EsploraError;
|
||||
use crate::types::{FullScanRequest, SyncRequest};
|
||||
use crate::wallet::Update;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use bdk::bitcoin::Transaction as BdkTransaction;
|
||||
use bdk::wallet::Update as BdkUpdate;
|
||||
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_esplora::esplora_client::{BlockingClient, Builder};
|
||||
use bdk_esplora::EsploraExt;
|
||||
|
||||
@ -17,40 +23,56 @@ impl EsploraClient {
|
||||
Self(client)
|
||||
}
|
||||
|
||||
// This is a temporary solution for scanning. The long-term solution involves not passing
|
||||
// the wallet to the client at all.
|
||||
pub fn full_scan(
|
||||
&self,
|
||||
wallet: Arc<Wallet>,
|
||||
request: Arc<FullScanRequest>,
|
||||
stop_gap: u64,
|
||||
parallel_requests: u64,
|
||||
) -> Result<Arc<Update>, EsploraError> {
|
||||
let wallet = wallet.get_wallet();
|
||||
|
||||
let previous_tip = wallet.latest_checkpoint();
|
||||
let keychain_spks = wallet.all_unbounded_spk_iters().into_iter().collect();
|
||||
|
||||
let (update_graph, last_active_indices) = self
|
||||
// using option and take is not ideal but the only way to take full ownership of the request
|
||||
let request: BdkFullScanRequest<KeychainKind> = request
|
||||
.0
|
||||
.full_scan(keychain_spks, stop_gap as usize, parallel_requests as usize)
|
||||
.map_err(|e| EsploraError::from(*e))?;
|
||||
.lock()
|
||||
.unwrap()
|
||||
.take()
|
||||
.ok_or(EsploraError::RequestAlreadyConsumed)?;
|
||||
|
||||
let missing_heights = update_graph.missing_heights(wallet.local_chain());
|
||||
let chain_update = self
|
||||
.0
|
||||
.update_local_chain(previous_tip, missing_heights)
|
||||
.map_err(|e| EsploraError::from(*e))?;
|
||||
let result: BdkFullScanResult<KeychainKind> =
|
||||
self.0
|
||||
.full_scan(request, stop_gap as usize, parallel_requests as usize)?;
|
||||
|
||||
let update = BdkUpdate {
|
||||
last_active_indices,
|
||||
graph: update_graph,
|
||||
chain: Some(chain_update),
|
||||
let update = bdk::wallet::Update {
|
||||
last_active_indices: result.last_active_indices,
|
||||
graph: result.graph_update,
|
||||
chain: Some(result.chain_update),
|
||||
};
|
||||
|
||||
Ok(Arc::new(Update(update)))
|
||||
}
|
||||
|
||||
// pub fn sync();
|
||||
pub fn sync(
|
||||
&self,
|
||||
request: Arc<SyncRequest>,
|
||||
parallel_requests: u64,
|
||||
) -> Result<Arc<Update>, EsploraError> {
|
||||
// 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(EsploraError::RequestAlreadyConsumed)?;
|
||||
|
||||
let result: BdkSyncResult = self.0.sync(request, parallel_requests as usize)?;
|
||||
|
||||
let update = bdk::wallet::Update {
|
||||
last_active_indices: BTreeMap::default(),
|
||||
graph: result.graph_update,
|
||||
chain: Some(result.chain_update),
|
||||
};
|
||||
|
||||
Ok(Arc::new(Update(update)))
|
||||
}
|
||||
|
||||
pub fn broadcast(&self, transaction: &Transaction) -> Result<(), EsploraError> {
|
||||
let bdk_transaction: BdkTransaction = transaction.into();
|
||||
|
@ -40,8 +40,10 @@ use crate::types::AddressInfo;
|
||||
use crate::types::Balance;
|
||||
use crate::types::CanonicalTx;
|
||||
use crate::types::ChainPosition;
|
||||
use crate::types::FullScanRequest;
|
||||
use crate::types::LocalOutput;
|
||||
use crate::types::ScriptAmount;
|
||||
use crate::types::SyncRequest;
|
||||
use crate::wallet::BumpFeeTxBuilder;
|
||||
use crate::wallet::SentAndReceivedValues;
|
||||
use crate::wallet::TxBuilder;
|
||||
|
@ -1,14 +1,15 @@
|
||||
use crate::bitcoin::{Address, OutPoint, Script, Transaction, TxOut};
|
||||
|
||||
use bdk::chain::spk_client::FullScanRequest as BdkFullScanRequest;
|
||||
use bdk::chain::spk_client::SyncRequest as BdkSyncRequest;
|
||||
use bdk::chain::tx_graph::CanonicalTx as BdkCanonicalTx;
|
||||
use bdk::chain::{ChainPosition as BdkChainPosition, ConfirmationTimeHeightAnchor};
|
||||
use bdk::wallet::AddressIndex as BdkAddressIndex;
|
||||
use bdk::wallet::AddressInfo as BdkAddressInfo;
|
||||
use bdk::wallet::Balance as BdkBalance;
|
||||
use bdk::KeychainKind;
|
||||
use bdk::LocalOutput as BdkLocalOutput;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ChainPosition {
|
||||
@ -108,3 +109,7 @@ impl From<BdkLocalOutput> for LocalOutput {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FullScanRequest(pub(crate) Mutex<Option<BdkFullScanRequest<KeychainKind>>>);
|
||||
|
||||
pub struct SyncRequest(pub(crate) Mutex<Option<BdkSyncRequest>>);
|
||||
|
@ -4,7 +4,9 @@ use crate::error::{
|
||||
CalculateFeeError, CannotConnectError, CreateTxError, PersistenceError, SignerError,
|
||||
TxidParseError, WalletCreationError,
|
||||
};
|
||||
use crate::types::{AddressIndex, AddressInfo, Balance, CanonicalTx, LocalOutput, ScriptAmount};
|
||||
use crate::types::{
|
||||
AddressInfo, Balance, CanonicalTx, FullScanRequest, LocalOutput, ScriptAmount, SyncRequest,
|
||||
};
|
||||
|
||||
use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf;
|
||||
use bdk::bitcoin::Network;
|
||||
@ -142,6 +144,16 @@ impl Wallet {
|
||||
pub fn list_output(&self) -> Vec<LocalOutput> {
|
||||
self.get_wallet().list_output().map(|o| o.into()).collect()
|
||||
}
|
||||
|
||||
pub fn start_full_scan(&self) -> Arc<FullScanRequest> {
|
||||
let request = self.get_wallet().start_full_scan();
|
||||
Arc::new(FullScanRequest(Mutex::new(Some(request))))
|
||||
}
|
||||
|
||||
pub fn start_sync_with_revealed_spks(&self) -> Arc<SyncRequest> {
|
||||
let request = self.get_wallet().start_sync_with_revealed_spks();
|
||||
Arc::new(SyncRequest(Mutex::new(Some(request))))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SentAndReceivedValues {
|
||||
|
@ -24,7 +24,8 @@ class LiveTxBuilderTest {
|
||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||
val wallet = Wallet(descriptor, null, persistenceFilePath, Network.TESTNET)
|
||||
val esploraClient = EsploraClient("https://esplora.testnet.kuutamo.cloud/")
|
||||
val update = esploraClient.fullScan(wallet, 10uL, 1uL)
|
||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||
wallet.applyUpdate(update)
|
||||
println("Balance: ${wallet.getBalance().total}")
|
||||
|
||||
@ -47,7 +48,8 @@ class LiveTxBuilderTest {
|
||||
val changeDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.TESTNET)
|
||||
val wallet = Wallet(externalDescriptor, changeDescriptor, persistenceFilePath, Network.TESTNET)
|
||||
val esploraClient = EsploraClient("https://esplora.testnet.kuutamo.cloud/")
|
||||
val update = esploraClient.fullScan(wallet, 10uL, 1uL)
|
||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||
wallet.applyUpdate(update)
|
||||
println("Balance: ${wallet.getBalance().total}")
|
||||
|
||||
|
@ -26,7 +26,8 @@ class LiveWalletTest {
|
||||
val esploraClient: EsploraClient = EsploraClient("https://esplora.testnet.kuutamo.cloud/")
|
||||
// val esploraClient: EsploraClient = EsploraClient("https://mempool.space/testnet/api")
|
||||
// val esploraClient = EsploraClient("https://blockstream.info/testnet/api")
|
||||
val update = esploraClient.fullScan(wallet, 10uL, 1uL)
|
||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||
wallet.applyUpdate(update)
|
||||
println("Balance: ${wallet.getBalance().total}")
|
||||
|
||||
@ -47,14 +48,14 @@ class LiveWalletTest {
|
||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||
val wallet: Wallet = Wallet(descriptor, null, persistenceFilePath, Network.TESTNET)
|
||||
val esploraClient = EsploraClient("https://esplora.testnet.kuutamo.cloud/")
|
||||
val update = esploraClient.fullScan(wallet, 10uL, 1uL)
|
||||
|
||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||
wallet.applyUpdate(update)
|
||||
println("Balance: ${wallet.getBalance().total}")
|
||||
println("New address: ${wallet.getAddress(AddressIndex.New).address.asString()}")
|
||||
println("New address: ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()}")
|
||||
|
||||
assert(wallet.getBalance().total > 0uL) {
|
||||
"Wallet balance must be greater than 0! Please send funds to ${wallet.getAddress(AddressIndex.New).address} and try again."
|
||||
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} and try again."
|
||||
}
|
||||
|
||||
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
|
||||
|
@ -42,7 +42,7 @@ class OfflineWalletTest {
|
||||
persistenceFilePath,
|
||||
Network.TESTNET
|
||||
)
|
||||
val addressInfo: AddressInfo = wallet.getAddress(AddressIndex.New)
|
||||
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
|
||||
|
||||
assertTrue(addressInfo.address.isValidForNetwork(Network.TESTNET), "Address is not valid for testnet network")
|
||||
assertTrue(addressInfo.address.isValidForNetwork(Network.SIGNET), "Address is not valid for signet network")
|
||||
|
@ -19,19 +19,20 @@ class LiveTxBuilderTest(unittest.TestCase):
|
||||
"./bdk_persistence.db",
|
||||
bdk.Network.TESTNET
|
||||
)
|
||||
esploraClient: bdk.EsploraClient = bdk.EsploraClient(url = "https://esplora.testnet.kuutamo.cloud/")
|
||||
update = esploraClient.full_scan(
|
||||
wallet = wallet,
|
||||
stop_gap = 10,
|
||||
parallel_requests = 1
|
||||
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = "https://esplora.testnet.kuutamo.cloud/")
|
||||
full_scan_request: bdk.FullScanRequest = wallet.start_full_scan()
|
||||
update = esplora_client.full_scan(
|
||||
full_scan_request=full_scan_request,
|
||||
stop_gap=10,
|
||||
parallel_requests=1
|
||||
)
|
||||
wallet.apply_update(update)
|
||||
|
||||
self.assertGreater(wallet.get_balance().total, 0)
|
||||
|
||||
recipient = bdk.Address(
|
||||
address = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||
network = bdk.Network.TESTNET
|
||||
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||
network=bdk.Network.TESTNET
|
||||
)
|
||||
|
||||
psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=4200).fee_rate(fee_rate=bdk.FeeRate.from_sat_per_vb(2)).finish(wallet)
|
||||
@ -53,23 +54,24 @@ class LiveTxBuilderTest(unittest.TestCase):
|
||||
"./bdk_persistence.db",
|
||||
bdk.Network.TESTNET
|
||||
)
|
||||
esploraClient: bdk.EsploraClient = bdk.EsploraClient(url = "https://esplora.testnet.kuutamo.cloud/")
|
||||
update = esploraClient.full_scan(
|
||||
wallet = wallet,
|
||||
stop_gap = 10,
|
||||
parallel_requests = 1
|
||||
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = "https://esplora.testnet.kuutamo.cloud/")
|
||||
full_scan_request: bdk.FullScanRequest = wallet.start_full_scan()
|
||||
update = esplora_client.full_scan(
|
||||
full_scan_request=full_scan_request,
|
||||
stop_gap=10,
|
||||
parallel_requests=1
|
||||
)
|
||||
wallet.apply_update(update)
|
||||
|
||||
self.assertGreater(wallet.get_balance().total, 0)
|
||||
|
||||
recipient1 = bdk.Address(
|
||||
address = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||
network = bdk.Network.TESTNET
|
||||
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||
network=bdk.Network.TESTNET
|
||||
)
|
||||
recipient2 = bdk.Address(
|
||||
address = "tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6",
|
||||
network = bdk.Network.TESTNET
|
||||
address="tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6",
|
||||
network=bdk.Network.TESTNET
|
||||
)
|
||||
all_recipients = list(
|
||||
bdk.ScriptAmount(recipient1.script_pubkey, 4200),
|
||||
|
@ -19,11 +19,12 @@ class LiveWalletTest(unittest.TestCase):
|
||||
"./bdk_persistence.db",
|
||||
bdk.Network.TESTNET
|
||||
)
|
||||
esploraClient: bdk.EsploraClient = bdk.EsploraClient(url = "https://esplora.testnet.kuutamo.cloud/")
|
||||
update = esploraClient.full_scan(
|
||||
wallet = wallet,
|
||||
stop_gap = 10,
|
||||
parallel_requests = 1
|
||||
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = "https://esplora.testnet.kuutamo.cloud/")
|
||||
full_scan_request: bdk.FullScanRequest = wallet.start_full_scan()
|
||||
update = esplora_client.full_scan(
|
||||
full_scan_request=full_scan_request,
|
||||
stop_gap=10,
|
||||
parallel_requests=1
|
||||
)
|
||||
wallet.apply_update(update)
|
||||
|
||||
@ -49,19 +50,20 @@ class LiveWalletTest(unittest.TestCase):
|
||||
"./bdk_persistence.db",
|
||||
bdk.Network.TESTNET
|
||||
)
|
||||
esploraClient: bdk.EsploraClient = bdk.EsploraClient(url = "https://esplora.testnet.kuutamo.cloud/")
|
||||
update = esploraClient.full_scan(
|
||||
wallet = wallet,
|
||||
stop_gap = 10,
|
||||
parallel_requests = 1
|
||||
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = "https://esplora.testnet.kuutamo.cloud/")
|
||||
full_scan_request: bdk.FullScanRequest = wallet.start_full_scan()
|
||||
update = esplora_client.full_scan(
|
||||
full_scan_request=full_scan_request,
|
||||
stop_gap=10,
|
||||
parallel_requests=1
|
||||
)
|
||||
wallet.apply_update(update)
|
||||
|
||||
self.assertGreater(wallet.get_balance().total, 0)
|
||||
|
||||
recipient = bdk.Address(
|
||||
address = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||
network = bdk.Network.TESTNET
|
||||
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||
network=bdk.Network.TESTNET
|
||||
)
|
||||
|
||||
psbt: bdk.Psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=4200).fee_rate(fee_rate=bdk.FeeRate.from_sat_per_vb(2)).finish(wallet)
|
||||
@ -77,7 +79,7 @@ class LiveWalletTest(unittest.TestCase):
|
||||
fee_rate = wallet.calculate_fee_rate(tx)
|
||||
print(f"Transaction Fee Rate: {fee_rate.to_sat_per_vb_ceil()} sat/vB")
|
||||
|
||||
esploraClient.broadcast(tx)
|
||||
esplora_client.broadcast(tx)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -19,7 +19,7 @@ class OfflineWalletTest(unittest.TestCase):
|
||||
"./bdk_persistence.db",
|
||||
bdk.Network.TESTNET
|
||||
)
|
||||
address_info: bdk.AddressInfo = wallet.get_address(bdk.AddressIndex.NEW())
|
||||
address_info: bdk.AddressInfo = wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL)
|
||||
|
||||
self.assertTrue(address_info.address.is_valid_for_network(bdk.Network.TESTNET), "Address is not valid for testnet network")
|
||||
self.assertTrue(address_info.address.is_valid_for_network(bdk.Network.SIGNET), "Address is not valid for signet network")
|
||||
|
@ -35,8 +35,9 @@ final class LiveTxBuilderTests: XCTestCase {
|
||||
network: .testnet
|
||||
)
|
||||
let esploraClient = EsploraClient(url: "https://esplora.testnet.kuutamo.cloud/")
|
||||
let fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||
let update = try esploraClient.fullScan(
|
||||
wallet: wallet,
|
||||
fullScanRequest: fullScanRequest,
|
||||
stopGap: 10,
|
||||
parallelRequests: 1
|
||||
)
|
||||
@ -70,8 +71,9 @@ final class LiveTxBuilderTests: XCTestCase {
|
||||
network: .testnet
|
||||
)
|
||||
let esploraClient = EsploraClient(url: "https://esplora.testnet.kuutamo.cloud/")
|
||||
let fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||
let update = try esploraClient.fullScan(
|
||||
wallet: wallet,
|
||||
fullScanRequest: fullScanRequest,
|
||||
stopGap: 10,
|
||||
parallelRequests: 1
|
||||
)
|
||||
|
@ -35,8 +35,9 @@ final class LiveWalletTests: XCTestCase {
|
||||
network: .testnet
|
||||
)
|
||||
let esploraClient = EsploraClient(url: "https://esplora.testnet.kuutamo.cloud/")
|
||||
let fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||
let update = try esploraClient.fullScan(
|
||||
wallet: wallet,
|
||||
fullScanRequest: fullScanRequest,
|
||||
stopGap: 10,
|
||||
parallelRequests: 1
|
||||
)
|
||||
@ -66,8 +67,9 @@ final class LiveWalletTests: XCTestCase {
|
||||
network: .testnet
|
||||
)
|
||||
let esploraClient = EsploraClient(url: "https://esplora.testnet.kuutamo.cloud/")
|
||||
let fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||
let update = try esploraClient.fullScan(
|
||||
wallet: wallet,
|
||||
fullScanRequest: fullScanRequest,
|
||||
stopGap: 10,
|
||||
parallelRequests: 1
|
||||
)
|
||||
|
@ -34,7 +34,7 @@ final class OfflineWalletTests: XCTestCase {
|
||||
persistenceBackendPath: dbFilePath.path,
|
||||
network: .testnet
|
||||
)
|
||||
let addressInfo: AddressInfo = wallet.getAddress(addressIndex: AddressIndex.new)
|
||||
let addressInfo: AddressInfo = try wallet.revealNextAddress(keychain: KeychainKind.external)
|
||||
|
||||
XCTAssertTrue(addressInfo.address.isValidForNetwork(network: Network.testnet),
|
||||
"Address is not valid for testnet network")
|
||||
|
Loading…
x
Reference in New Issue
Block a user