benny b's blog
5D34 2974 4CB5 F205 76ED A743 B518 AFC2 A836 3DD9
Introduction to DlcDevKit
tl;dr: dlcs are so back
When I was first exploring the DLC ecosystem I wanted an easy way to prototype my idea. There are excellent libraries for DLCs but the application tooling fell short to quickly prototype. I found this a hard stop for application developers. If someone new to bitcoin development were to start with DLCs they would be encumbered with Bitcoin implementation details before even starting their application. In part, this is why DLCs never got their chance to shine in the open market.
This lead me to create dlcdevkit
(or ddk
). An
application development kit that manages contract creation, management,
storage, message handling, and comes with a bdk
wallet out
of the box. Shifting the focus for application developers to focus on
their ideas, not bitcoin internal complexities.
ddk
is written in rust btw 🦀. It uses
rust-dlc under
the hood for contract management, execution, and creation of DLC
transactions. rust-dlc
lacks a direct wallet interface so
ddk
is packaged with bdk
in a nice bow for you
to create your next billion dollar idea.
-
Sports betting app?
ddk
is here for you -
Insurance claims?
ddk
is here for you -
Derivatives trading?
ddk
is here for you
Usage of ddk
Creating a fully features DLC application is straightforward. App developers just need to answer 3 questions.
- How will my users communicate with each other?.
- What database will I use to store contracts?
- What oracle will I use?
ddk
composes to three main traits that consumers will
implements to handle those operations internally. If you are familiar
with the project
ldk-node then
the ddk
API will feel very familiar.
First a full example
use ddk::builder::Builder;
use ddk::storage::SledStorage;
use ddk::transport::lightning::LightningTransport; // with "lightning" feature
use ddk::oracle::KormirOracleClient;
use bitcoin::Network;
use std::sync::Arc;
#[tokio::main]
fn main() {
let transport = Arc::new(LightningTransport::new([0u8;32], 9735, Network::Signet));
let storage = Arc::new(SledStorage::new("/tmp/ddk")?);
let oracle_client = Arc::new(KormirOracleClient::new("https://kormir.dlcdevkit.com").await?);
let ddk: ApplicationDdk = Builder::new()
.set_seed_bytes([0u8;32])
.set_network(Network::Regtest)
.set_esplora_path("http://mutinynet.com") // shouts out mutiny
.set_transport(transport.clone())
.set_storage(storage.clone())
.set_oracle(oracle_client.clone())
.finish()
.expect("skill issue");
ddk.start().expect("skill issue");
}
The builder takes in information like seed_bytes for the internal
wallet, network to be on, esplora host, and then the consumer creates
the necessary traits for transport, storage, and oracle.
ddk
have three example crates out of the box to quickly get
started.
- Lightning transport - BOLT 8 compliant messaging using the LDK peer manager
- Sled Storage - file based storage using sleddb
- Kormir Oracle Client - Server implementation of the kormir crate
Transport Trait
DLC participants need some way to communicate with each other. Passing
messages such as Offers, Accept, and Sign. Typically app developers
would have to build and maintain this logic by themselves but
ddk
abstracts this with the Transport
trait.
#[async_trait]
/// Allows ddk to open a listening connection and send/receive dlc messages functionality.
pub trait Transport: Send + Sync + 'static {
/// Name for the transport service.
fn name(&self) -> String;
/// Open an incoming listener for DLC messages from peers.
async fn listen(&self);
/// Get messages that have not been processed yet.
async fn receive_messages<S: Storage, O: Oracle>(
&self,
manager: Arc<DlcDevKitDlcManager<S, O>>,
);
/// Send a message to a specific counterparty.
fn send_message(&self, counterparty: PublicKey, message: Message);
/// Connect to another peer
async fn connect_outbound(&self, pubkey: PublicKey, host: &str);
}
LightnigTransport
example that is available
Consumers implement a listener for which ddk
will listen on
for incoming connections. Then a function for receving messages, this is
passed to the manager to handle the corresponding DLC message. And then
of course functions for sending messages and connecting to
counterparties.
This opens up an opportunity for consumers to implement a transport
listener for whatever platform they want to build on. You can create a
nostr
implementation for monile applications that have
dunamic IP addresses. You canbuild a client/server model for passing
messages between counterparties. Or you could use the lightning gossip
layer!
Storage Trait
The storage trait is used for the storage of contracts, wallet
changesets, and counterparty peer information. It is a super trait of
the
rust-dlc/dlc-manager
trait. It is technically a super trait
for bdk
but there is a wrapper struct that is created
because of the bdk::PersistedWallet
trait bounds.
/// Storage for DLC contracts.
pub trait Storage: dlc_manager::Storage + Send + Sync + 'static {
///// Instantiate the storage for the BDK wallet.
fn initialize_bdk(&self) -> Result<ChangeSet, WalletError>;
/// Save changeset to the wallet storage.
fn persist_bdk(&self, changeset: &ChangeSet) -> Result<(), WalletError>;
/// Connected counterparties.
fn list_peers(&self) -> anyhow::Result<Vec<PeerInformation>>;
/// Persis counterparty.
fn save_peer(&self, peer: PeerInformation) -> anyhow::Result<()>;
// For another blog!
// #[cfg(feature = "marketplace")]
fn save_announcement(&self, announcement: OracleAnnouncement) -> anyhow::Result<()>;
// #[cfg(feature = "marketplace")]
fn get_marketplace_announcements(&self) -> anyhow::Result<Vec<OracleAnnouncement>>;
}
Oracle Trait
There is not much to the oracle client. Similar to the storage trait it
is a super trait to rust-dlc/dlc-manager
storage trait. All
that is required is get_announcement()
and
get_attestation()
. If consumers are interested in creating
their own oracle implementation, I recommend using
kormir
.
/// Oracle client
pub trait Oracle: dlc_manager::Oracle + Send + Sync + 'static {
fn name(&self) -> String;
}
How it glues together
Now with all of the traits implemented and built with the
ddk::builder::Builder
. Calling the
start()
method starts the listeners, processes dlc messages, checks contracts,
and syncs the on-chain wallet.
pub fn start(&self) -> anyhow::Result<()> {
let mut runtime_lock = self.runtime.write().unwrap();
if runtime_lock.is_some() {
return Err(anyhow!("DDK is still running."));
}
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?;
let manager_clone = self.manager.clone();
let receiver_clone = self.receiver.clone();
runtime.spawn(async move { Self::run_manager(manager_clone, receiver_clone).await });
let transport_clone = self.transport.clone();
runtime.spawn(async move {
transport_clone.listen().await;
});
let transport_clone = self.transport.clone();
let manager_clone = self.manager.clone();
runtime.spawn(async move {
transport_clone.receive_messages(manager_clone).await;
});
let wallet_clone = self.wallet.clone();
runtime.spawn(async move {
let mut timer = tokio::time::interval(Duration::from_secs(10));
loop {
timer.tick().await;
wallet_clone.sync().unwrap();
}
});
let processor = self.sender.clone();
runtime.spawn(async move {
let mut timer = tokio::time::interval(Duration::from_secs(5));
loop {
timer.tick().await;
processor
.send(DlcManagerMessage::PeriodicCheck)
.expect("couldn't send periodic check");
}
});
*runtime_lock = Some(runtime);
Ok(())
}
Flexibility
Like I mentioned before, ddk
is flexible for whatever
platform you are building your app on. Want to use
nostr
for transport? Create a nostr transport! Create a
relational database with sql or go document based with mongo. You can
create a gRPC server or a REST service.
ddk-node
Don't want to code and start using DLCs? Check out
ddk-node
. A pre-built node and cli using the pre-build components maintained in
dlcdevkkit
.
Installation
Start building with dlcdevkit
today!
$ cargo add ddk
Start executing DLCs today!
$ cargo install ddk-node
Currently, ddk requires an async runtime which is not merged yet with
rust-dlc
so you may have to install or add with the github
link. But there is a
passing PR
$ cargo add --git https://github.com/bennyhodl/dlcdevkit.git ddk
$ cargo install --git https://github.com/bennyhodl/dlcdevkit.git ddk-node
Contribute to ddk
I am looking for contributers and users of dlcdevkit
. I
extend an invitation to participate.
Star the project on
GitHub
Read the
documentation Follow me on
twitter