use crate::logger::{log_error, log_info, WriteLog};
use bip39::{Language, Mnemonic};
use bitcoin::key::rand::Fill;
use bitcoin::Network;
use ddk_manager::manager::Manager;
use ddk_manager::SystemTimeProvider;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use crate::chain::EsploraClient;
use crate::ddk::{DlcDevKit, DlcManagerMessage};
use crate::error::{BuilderError, Error};
use crate::logger::{LogLevel, Logger};
use crate::wallet::address::AddressGenerator;
use crate::wallet::DlcDevKitWallet;
use crate::{Oracle, Storage, Transport};
const DEFAULT_ESPLORA_HOST: &str = "https://mutinynet.com/api";
const DEFAULT_NETWORK: Network = Network::Signet;
const DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Info;
#[derive(Debug, Clone)]
pub enum SeedConfig {
Random,
Mnemonic(String, String),
Bytes([u8; 64]),
}
#[derive(Clone)]
pub struct Builder<T, S, O> {
name: Option<String>,
transport: Option<Arc<T>>,
storage: Option<Arc<S>>,
oracle: Option<Arc<O>>,
contract_address_generator: Option<Arc<dyn AddressGenerator + Send + Sync + 'static>>,
esplora_host: String,
network: Network,
seed_bytes: [u8; 64],
logger: Option<Arc<Logger>>,
}
impl<T: Transport, S: Storage, O: Oracle> Default for Builder<T, S, O> {
fn default() -> Self {
Self {
name: None,
transport: None,
storage: None,
oracle: None,
contract_address_generator: None,
esplora_host: DEFAULT_ESPLORA_HOST.to_string(),
network: DEFAULT_NETWORK,
seed_bytes: [0u8; 64],
logger: None,
}
}
}
impl<T: Transport, S: Storage, O: Oracle> Builder<T, S, O> {
pub fn new() -> Self {
Builder::default()
}
pub fn set_name(&mut self, name: &str) -> &mut Self {
self.name = Some(name.into());
self
}
pub fn set_transport(&mut self, transport: Arc<T>) -> &mut Self {
self.transport = Some(transport);
self
}
pub fn set_storage(&mut self, storage: Arc<S>) -> &mut Self {
self.storage = Some(storage);
self
}
pub fn set_oracle(&mut self, oracle: Arc<O>) -> &mut Self {
self.oracle = Some(oracle);
self
}
pub fn set_contract_address_generator(
&mut self,
contract_address_generator: Arc<dyn AddressGenerator + Send + Sync + 'static>,
) -> &mut Self {
self.contract_address_generator = Some(contract_address_generator);
self
}
pub fn set_esplora_host(&mut self, host: String) -> &mut Self {
self.esplora_host = host;
self
}
pub fn set_network(&mut self, network: Network) -> &mut Self {
self.network = network;
self
}
pub fn set_seed_bytes(&mut self, seed_config: SeedConfig) -> Result<&mut Self, BuilderError> {
match seed_config {
SeedConfig::Random => {
let mut seed = [0u8; 64];
seed.try_fill(&mut bitcoin::key::rand::thread_rng())
.map_err(|_| BuilderError::SeedGenerationFailed)?;
self.seed_bytes = seed
}
SeedConfig::Mnemonic(mnemonic, passphrase) => {
let mnemonic = Mnemonic::parse_in_normalized(Language::English, &mnemonic).unwrap();
self.seed_bytes = mnemonic.to_seed(passphrase)
}
SeedConfig::Bytes(bytes) => self.seed_bytes = bytes,
}
Ok(self)
}
pub fn set_logger(&mut self, logger: Arc<Logger>) -> &mut Self {
self.logger = Some(logger);
self
}
fn setup_logger(&self, name: &str) -> Result<Arc<Logger>, Error> {
match &self.logger {
Some(logger) => Ok(logger.clone()),
None => {
Ok(Arc::new(Logger::console(
name.to_string(),
DEFAULT_LOG_LEVEL,
)))
}
}
}
#[tracing::instrument(name = "builder", skip(self))]
pub async fn finish(&self) -> Result<DlcDevKit<T, S, O>, Error> {
let transport = self
.transport
.as_ref()
.map_or_else(|| Err(BuilderError::NoTransport), |t| Ok(t.clone()))?;
let storage = self
.storage
.as_ref()
.map_or_else(|| Err(BuilderError::NoStorage), |s| Ok(s.clone()))?;
let oracle = self
.oracle
.as_ref()
.map_or_else(|| Err(BuilderError::NoOracle), |o| Ok(o.clone()))?;
let name = self
.name
.clone()
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
let logger = self.setup_logger(&name)?;
let esplora_client = Arc::new(EsploraClient::new(
&self.esplora_host,
self.network,
logger.clone(),
)?);
let wallet = match &self.contract_address_generator {
Some(w) => {
let wallet = DlcDevKitWallet::new(
&self.seed_bytes,
esplora_client.clone(),
self.network,
storage.clone(),
Some(w.clone()),
logger.clone(),
)
.await?;
Arc::new(wallet)
}
None => Arc::new(
DlcDevKitWallet::new(
&self.seed_bytes,
esplora_client.clone(),
self.network,
storage.clone(),
None,
logger.clone(),
)
.await?,
),
};
let mut oracles = HashMap::new();
oracles.insert(oracle.get_public_key(), oracle.clone());
let (sender, mut receiver) = tokio::sync::mpsc::channel(100);
let (stop_signal_sender, stop_signal) = tokio::sync::watch::channel(false);
let manager = Arc::new(
Manager::new(
wallet.clone(),
wallet.clone(),
esplora_client.clone(),
storage.clone(),
oracles,
Arc::new(SystemTimeProvider {}),
wallet.clone(),
logger.clone(),
)
.await?,
);
let manager_clone = manager.clone();
let logger_clone = logger.clone();
tokio::spawn(async move {
while let Some(msg) = receiver.recv().await {
match msg {
DlcManagerMessage::OfferDlc {
contract_input,
counter_party,
oracle_announcements,
responder,
} => {
let offer = manager_clone
.send_offer_with_announcements(
&contract_input,
counter_party,
vec![oracle_announcements],
)
.await;
let _ = responder.send(offer).map_err(|e| {
log_error!(logger_clone.clone(), "Error sending offer: {:?}", e);
});
}
DlcManagerMessage::AcceptDlc {
contract,
responder,
} => {
let accept_dlc = manager_clone.accept_contract_offer(&contract).await;
let _ = responder.send(accept_dlc).map_err(|e| {
log_error!(logger_clone.clone(), "Error sending accept DLC: {:?}", e);
});
}
DlcManagerMessage::PeriodicCheck => {
let _ = manager_clone.periodic_check(false).await;
}
}
}
});
log_info!(
logger.clone(),
"DDK runtime created. name={}, esplora={}, network={}, transport={}, oracle={}",
name,
self.esplora_host,
self.network,
transport.name(),
oracle.get_public_key()
);
Ok(DlcDevKit {
runtime: Arc::new(RwLock::new(None)),
wallet,
manager,
sender,
transport,
storage,
oracle,
network: self.network,
stop_signal,
stop_signal_sender,
logger,
})
}
}