Discreet Log Contracts

Interacting with Bitcoin Contracts in the Leather


This documentation shows dApp developers how to access Bitcoin Contracts in their applications, via Leather.
Enabling the interfacing of Bitcoin Contracts via Leather requires three steps:
  1. 1.
    Integrate your smart contract with the dlc-manager smart contract
  2. 2.
    Set up and run a Router Wallet
  3. 3.
    Configure your web app to support Bitcoin Contracts, consisting of two parts:
    1. 1.
      Configure your web app to interact with the Router wallet
    2. 2.
      Configure your web app to interact with the Leather API
See the below sections for more details on each step.
Note Bitcoin Contracts are powered by the underlying technology known as DLCs—Discreet Log Contracts—and sometimes the names are used interchangeably. Learn more about DLCs here →

Step 1—Solidity / Clarity Smart Contract Integration

To enable the use of DLCs, which let users transact with native Bitcoin directly, it's essential to acquaint yourself with either DLC.Link's Clarity or Solidity contract. These are referred to as the DLC Manager contracts. Calling into the DLC Manager contract happens directly from your smart contract. See the following instructions for more details:
Registering Your Smart Contract
As the DLC.Link product is currently in the beta-testing phase, you must first register your contract with the DLC.Link development team in our Discord server. Once you deploy your contract, please share it's address with the DLC.Link developers to have it whitelisted. Please contact DLC.Link on their Discord server.

Step 2—Router Wallet Communication

Bitcoin Contracts are powered by the underlying technology known as DLCs, Discreet Log Contracts. These contracts involve two parties, an offeror, and an acceptor. This is where the Router Wallet comes in. In the current setup of the DLC.Link solution, the user's Leather extension acts as the acceptor and the Router Wallet acts as the offeror. But the Router Wallet is more than just a Bitcoin wallet. It communicates with both the dlc-manager and the attestors and manages the lifecycle of a Bitcoin Contract, from creation to closure.
When a user wishes to accept a Bitcoin Contract and invokes the specified function, the dApp is expected to retrieve a Bitcoin Contract offer from the Router Wallet. Once the request arrives at the Router Wallet, it then creates a Bitcoin Contract with the given parameters and sends it to the user's wallet (Leather). The user can then accept the offer and the Router Wallet will manage the lifecycle of the Bitcoin Contract.

Router Wallet Setup

On how to set up the Router Wallet, please refer to the following documentation on the DLCLink Github.
Registering Your Router Wallet
Once you have set up your Router Wallet, share its address with the DLC.Link developers, as this also needs to be whitelisted during beta-testing. By doing so it will be granted permission for it to interact with our dlc-manager contract.

Step 3—dApp API Integration with the Router Wallet and Leather

Router Wallet API Integration

To integrate your dApp with the Router Wallet, follow these steps:
  1. 1.
    Request a Bitcoin Contract using the dlc-manager contract. This will provide you with a unique identifier, or UUID.
  2. 2.
    Use the UUID and other Bitcoin Contract parameters obtained from the smart contract or set them directly in your dApp.
  3. 3.
    From the dApp, send these parameters to the Router Wallet using the offer API endpoint.
The required parameters for the offer API endpoint are as follows:
  • uuid: The unique identifier of the Bitcoin Contract.
  • acceptCollateral: The amount of collateral the acceptor (user's wallet) is willing to deposit.
  • offerCollateral: The amount of collateral the offeror (Router Wallet) is willing to deposit. Note In the current setup of the DLC Link solution, this should always be 0.
  • totalOutcomes: The total number of outcomes of the Bitcoin Contract.
  • attestorList: The list of attestors that will be involved in the Bitcoin Contract.
By providing these parameters to the Router Wallet, you can successfully fetch an offer for the Bitcoin Contract.
To fetch an offer from the Router Wallet, you should use a function similar to the one below:
export const fetchBitcoinContractOfferFromRouterWallet = async (
uuid: string,
acceptCollateral: Number,
offerCollateral: Number,
totalOutcomes: Number,
attestorList: string[]
) => {
const counterpartyWalletURL: string = ''; // The Router Wallet Offer API endpoint.
const attestorListJSON = JSON.stringify(attestorList); // Convert attestorList to JSON string.
const response = await fetch(counterpartyWalletURL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
attestorList: attestorListJSON,
const bitcoinContractOffer = await response.json();
The function above returns a Bitcoin Contract Offer in string format, which you can then utilize to accept the offer in the next step.

Leather API Integration

To accept the received offer and engage with a wallet, you must communicate with the Leather extension via the Wallet API.
The parameters for the API are as follows:
interface CounterPartyWalletDetails {
counterpartyWalletURL: string; // The URL of the counterparty wallet.
counterpartyWalletName: string; // The name of the counterparty wallet.
counterpartyWalletIcon: string; // The icon associated with the counterparty wallet.
interface BitcoinContractRequestParams {
bitcoinContractOffer: string; // The Bitcoin Contract Offer in string format.
attestorURLs: string; // An array of attestor URLs (string[]) converted to JSON string.
counterpartyWalletDetails: string; // A CounterPartyWalletDetails object converted to JSON string.
Ensure that all the necessary fields are JSON-stringified before utilizing them as param, as the API operates using JSON format.
// receivedBitcoinContractOffer is the Bitcoin Contract Offer received from the Router Wallet.
const bitcoinContractOffer: string = JSON.stringify(receivedBitcoinContractOffer);
// attestorURLs is an array of attestor URLs, recieved from the Smart Contract object.
const attestorURLs: string = JSON.stringify([
// counterpartyWalletDetails is the details of the Router Wallet.
const counterpartyWalletDetails: CounterPartyWalletDetails = JSON.stringify({
counterpartyWalletURL: '',
counterpartyWalletName: 'Counterparty Wallet',
counterpartyWalletIcon: '',
const requestParams: BitcoinContractRequestParams = {
bitcoinContractOffer: bitcoinContractOffer,
attestorURLs: attestorURLs,
counterpartyWalletDetails: counterpartyWalletDetails,
const response = await window.btc.request('acceptBitcoinContractOffer', requestParams);
The response of the API call will be an object with the following fields:
interface BitcoinContractResponseBody {
contractId: string;
action: string;
txId?: string;
error?: string;
Upon accepting the offer, the response will include the action field set to accept, along with the broadcasted transaction ID (txId) and the corresponding contractId. If the user rejects the offer, the response will show the action field as reject, and also provide the contractId used to fetch the offer. In case the broadcast fails, the response will indicate action as failed, and include the contractId associated with the offer retrieval.
You can use the txId to check the status of the transaction on the blockchain.
if (response.result.action === 'accept') {
const txId = response.result.txId;
This function will pop up a window that will allow the user to review and accept the offer, and then sign and broadcast the contract.
Leather Popup Window