All pages
Powered by GitBook
1 of 11

tBTC SDK

tBTC SDK is a TypeScript library that provides effortless access to the fundamental features of the tBTC Bitcoin bridge. The SDK allows developers to integrate tBTC into their own applications and offer the power of trustless tokenized Bitcoin to their users.

The SDK documentation consists of the following parts:

  • Quickstart

  • Architecture

  • Guides

  • API Reference

For a high-level overview of the tBTC protocol, please see the tBTC Bitcoin Bridge section.

Quickstart

Here you can find instructions explaining how to use the SDK in your own project.

Installation

To install the tBTC SDK in your project, run:

yarn add @keep-network/tbtc-v2.ts
npm i @keep-network/tbtc-v2.ts

Please note that you will also need to install the ethers v5 library to initialize a signer or provider. To do so, invoke:

yarn add ethers@legacy-v5
npm i ethers@legacy-v5
The SDK depends on ethers v5. Proper support for newer ethers versions is 
not guaranteed right now.

Usage

Here is a brief example demonstrating the use of the SDK in Ethereum:

// Import SDK entrypoint component.
import { TBTC } from "@keep-network/tbtc-v2.ts"

// Create an instance of ethers signer.
const signer = (...)

// Initialize the SDK for Ethereum only.
const sdk = await TBTC.initializeMainnet(signer)

// Access SDK features.
sdk.deposits.(...)
sdk.redemptions.(...)

// Access tBTC smart contracts directly.
sdk.tbtcContracts.(...)

// Access Bitcoin client directly.
sdk.bitcoinClient.(...)

Crosschain

Here is a brief example demonstrating the use of the SDK in some L2, e.g. Arbitrum:

// Import SDK entrypoint component.
import { TBTC } from "@keep-network/tbtc-v2.ts"

// Create an instance of ethers provider.
const ethProvider = (...)

// Create an instance of ethers signer.
const arbitrumSigner = (...)

// Initialize the SDK for Ethereum only.
const sdk = await TBTC.initializeMainnet(ethProvider, true)

// Initialize it for any L2 (E.g., Arbitrum)
await sdk.initializeCrossChain("Arbitrum", arbitrumSigner);

// Access SDK features.
sdk.deposits.(...)
sdk.redemptions.(...)

// Access tBTC smart contracts directly.
sdk.tbtcContracts.(...)

// Access Bitcoin client directly.
sdk.bitcoinClient.(...)

Architecture

The following diagram presents the architecture of the tBTC SDK and its place in the ecosystem:

tBTC SDK Architecture

As you can see, the SDK consists of several major parts:

  • TBTC component

  • Feature services

  • Shared libraries

TBTC component

The TBTC component is the main entry point to the SDK. It provides different ways to initialize the SDK that are supposed to address the common use cases (see Initialize SDK guide to learn more). Moreover, the TBTC component gives access to the SDK core features through feature services as well as low-level direct access to the underlying tBTC smart contracts and Bitcoin network client.

Feature services

The role of the SDK feature services is to provide seamless access to the core features of the tBTC bridge. The most important feature services of the SDK are:

  • Deposits: deposit and mint flow for BTC depositors

  • Redemptions: unmint and redeem flow for TBTC redeemers

  • Maintenance: authorized maintenance actions for maintainers (e.g. optimistic minting guardians)

Shared libraries

Shared libraries are reusable modules that provide cross-cutting concerns leveraged by multiple feature services and the TBTC component. The main shared libraries of the SDK are:

  • Bitcoin: interfaces and utilities necessary to interact with the Bitcoin chain

  • Contracts: chain-agnostic interfaces allowing to interact with tBTC smart contracts

  • Electrum: Electrum-based implementation of the Bitcoin client

  • Ethereum: implementations of tBTC smart contract interfaces for Ethereum chain

  • Utils: general utility functions

Guides

Initialize SDK

Before initializing the tBTC SDK instance in your project, you need to answer the following questions:

  • What Bitcoin network and which tBTC contracts do you plan to interact with?

  • Do you want to perform just read-only actions or send transactions as well?

Answering those questions will allow initializing the SDK in the right way. The SDK is prepared to handle common use cases but provides some flexibility as well. This guide explains that in detail.

Specific guides

These guides will show you how to initialize the SDK in mainnet, testnet, crosschain or custom mode.

Ethereum and Bitcoin mainnetEthereum and Bitcoin testnetCrosschainCustom mode

Ethereum and Bitcoin mainnet

This is the most common case when you want to use the SDK to interact with the tBTC bridge on Ethereum and Bitcoin mainnet.

The SDK addresses it by exposing the TBTC.initializeMainnet function. That function:

  • Takes an Ethers signer or provider as a parameter and uses it to interact with tBTC contracts deployed on Ethereum mainnet

  • Automatically configures an Electrum Bitcoin client to communicate with the Bitcoin mainnet (communication is done through a set of pre-configured Electrum servers)

The following code snippet presents the usage of this function:

import * as ethers from "ethers"
import { TBTC } from "@keep-network/tbtc-v2.ts"

// Create an Ethers provider. Pass the URL of an Ethereum mainnet node.
// For example, Alchemy or Infura.
const provider = new ethers.providers.JsonRpcProvider("...")
// Create an Ethers signer. Pass the private key and the above provider.
const signer = new ethers.Wallet("...", provider)

// If you want to initialize the SDK just for read-only actions, it is
// enough to pass the provider. 
const sdkReadonly = await TBTC.initializeMainnet(provider)
// If you want to make transactions as well, you have to pass the signer.
const sdk = await TBTC.initializeMainnet(signer)

The above code snippet presents just one way of creating an Ethers signer/provider. Please refer Ethers v5 documentation to learn more.

Ethereum and Bitcoin testnet

This is the case where you want to use the SDK to interact with the tBTC bridge on Ethereum and Bitcoin testnet.

The SDK addresses it by exposing the TBTC.initializeSepolia function. That function:

  • Takes an Ethers signer or provider as a parameter and uses it to interact with tBTC contracts deployed on Ethereum Sepolia

  • Automatically configures an Electrum Bitcoin client to communicate with the Bitcoin testnet (communication is done through a set of pre-configured Electrum servers)

The usage of this function is exactly the same as for the previous TBTC.initializeMainnet function.

Crosschain

This mode of the SDK will initialize Ethereum, Bitcoin and an L2 to work with tBTC. Compatible with mainnet and testnet.

Currently only functional with Arbitrum, soon more blockchains will be available.

Mainnet

The SDK addresses it by exposing the TBTC.initializeMainnet function. That function:

  • Takes an Ethers signer or provider as a parameter and uses it to interact with tBTC contracts deployed on Ethereum mainnet

  • Needs to receive as a second parameter the value true to activate the crosschain mode.

  • Automatically configures an Electrum Bitcoin client to communicate with the Bitcoin mainnet (communication is done through a set of pre-configured Electrum servers)

The following code snippet presents the usage of this function:

import * as ethers from "ethers"
import { TBTC } from "@keep-network/tbtc-v2.ts"

// Create an Ethers provider. Pass the URL of an Ethereum and Arbitrum mainnet node.
// For example, Alchemy or Infura.
const ethProvider = new ethers.providers.JsonRpcProvider("...")
const arbProvider = new ethers.providers.JsonRpcProvider("...")

// Create an Ethers signer. Pass the private key and the above provider.
const arbSigner = new ethers.Wallet("...", provider)

// If you want to initialize the SDK, it is enough to pass the provider.
const sdk = await TBTC.initializeMainnet(ethProvider, true)

// Initialize cross-chain functionality for TBTC on Arbitrum
await sdk.initializeCrossChain("Arbitrum", arbSigner);

// The SDK will be ready to launch read and write in Arbitrum. 

The above code snippet presents just one way of creating an Ethers signer/provider. Please refer Ethers v5 documentation to learn more.

Testnet

The SDK addresses it by exposing the TBTC.initializeSepolia function. That function:

  • Takes an Ethers signer or provider as a parameter and uses it to interact with tBTC contracts deployed on Ethereum Sepolia

  • Needs to receive as a second parameter the value true to activate the crosschain mode.

  • Automatically configures an Electrum Bitcoin client to communicate with the Bitcoin testnet (communication is done through a set of pre-configured Electrum servers)

The usage of this function is exactly the same as for the previous TBTC.initializeMainnet function.

import * as ethers from "ethers"
import { TBTC } from "@keep-network/tbtc-v2.ts"

// Create an Ethers provider. Pass the URL of an Ethereum and Arbitrum testnet node.
// For example, Alchemy or Infura.
const ethProvider = new ethers.providers.JsonRpcProvider("...")
const arbProvider = new ethers.providers.JsonRpcProvider("...")

// Create an Ethers signer. Pass the private key and the above provider.
const arbSigner = new ethers.Wallet("...", provider)

// If you want to initialize the SDK, it is enough to pass the provider.
const sdk = await TBTC.initializeSepolia(ethProvider, true)

// Initialize cross-chain functionality for TBTC on Arbitrum
await sdk.initializeCrossChain("Arbitrum", arbSigner);

// The SDK will be ready to launch read and write in Arbitrum. 

Custom mode

Apart from the opinionated initialization functions, the SDK provides a flexible TBTC.initializeCustom function for advanced users.

This function allows setting up the SDK to work with custom tBTC smart contracts and custom Bitcoin network. For example, it can be used to address the following use cases:

Using locally deployed tBTC contracts and local Bitcoin network

This is the common case when you are setting up a development environment. Let's suppose you have deployed the tBTC contracts on your local Ethereum chain and you are using a local Bitcoin regtest node. The following snippet demonstrates SDK initialization in that case:

import * as ethers from "ethers"
import {
  TBTC,
  TBTCContracts,
  EthereumBridge,
  EthereumTBTCToken,
  EthereumTBTCVault,
  EthereumWalletRegistry,
  ElectrumClient,
} from "@keep-network/tbtc-v2.ts"

// Create an Ethers provider. Pass the URL of your local Ethereum node.
const provider = new ethers.providers.JsonRpcProvider("...")
// Create an Ethers signer. Pass the private key and the above provider.
const signer = new ethers.Wallet("...", provider)

// Create a reference to your locally deployed tBTC contracts.
// ABI will be loaded from the following contract packages:
// - @keep-network/tbtc-v2
// - @keep-network/ecdsa
const tbtcContracts: TBTCContracts = {
  bridge: new EthereumBridge({address: "...", signerOrProvider: signer}),
  tbtcToken: new EthereumTBTCToken({address: "...", signerOrProvider: signer}),
  tbtcVault: new EthereumTBTCVault({address: "...", signerOrProvider: signer}),
  walletRegistry: new EthereumWalletRegistry({address: "...", signerOrProvider: signer})
}

// Create an Electrum Bitcoin client pointing to your local regtest node.
const bitcoinClient = ElectrumClient.fromUrl("...")

// Initialize the SDK.
const sdk = await TBTC.initializeCustom(tbtcContracts, bitcoinClient)

Using a custom Bitcoin client instead of the automatically configured one

In some cases, you may want to initialize the SDK for mainnet/testnet, but point the Electrum Bitcoin client to your own server. Here is how you can do that:

import * as ethers from "ethers"
import {
  TBTC,
  TBTCContracts,
  loadEthereumContracts,
  ElectrumClient
} from "@keep-network/tbtc-v2.ts"

// Create an Ethers provider. Pass a URL of an Ethereum mainnet node.
// For example, Alchemy or Infura.
const provider = new ethers.providers.JsonRpcProvider("...")
// Create an Ethers signer. Pass the private key and the above provider.
const signer = new ethers.Wallet("...", provider)

// Load tBTC Ethereum mainnet contracts manually.
const tbtcContracts: TBTCContracts = await loadEthereumContracts(signer, "mainnet")

// Create an Electrum Bitcoin client pointing to your own mainnet server.
const bitcoinClient = ElectrumClient.fromUrl("...")

// Initialize the SDK with Electrum Bitcoin client pointing to your own server.
const sdk = await TBTC.initializeCustom(tbtcContracts, bitcoinClient)

You can even use another (non-Electrum) Bitcoin client implementation. This is how you can achieve that:

import * as ethers from "ethers"
import {
  TBTC,
  TBTCContracts,
  loadEthereumContracts,
  BitcoinClient,
} from "@keep-network/tbtc-v2.ts"

// Create an Ethers provider. Pass a URL of an Ethereum mainnet node.
// For example, Alchemy or Infura.
const provider = new ethers.providers.JsonRpcProvider("...")
// Create an Ethers signer. Pass the private key and the above provider.
const signer = new ethers.Wallet("...", provider)

// Load tBTC Ethereum mainnet contracts manually.
const tbtcContracts: TBTCContracts = await loadEthereumContracts(signer, "mainnet")

// Create your own Bitcoin client implementation.
class OwnBitcoinClientImpl implements BitcoinClient {
  // Implement all required methods
}

// Initialize your own Bitcoin client implementation.
const bitcoinClient = new OwnBitcoinClientImpl()

// Initialize the SDK with your own Bitcoin client implementation.
const sdk = await TBTC.initializeCustom(tbtcContracts, bitcoinClient)

Using mock tBTC contracts and mock Bitcoin network

Sometimes, you may also want to initialize the SDK with mock tBTC contracts and Bitcoin network for testing purposes. This can be done as follows:

import {
  TBTC,
  TBTCContracts,
  Bridge,
  TBTCToken,
  TBTCVault,
  WalletRegistry,
  BitcoinClient,
} from "@keep-network/tbtc-v2.ts"

// Create mock Bridge contract implementation.
class MockBridge implements Bridge {
  // Implement all required methods
}

// Create mock TBTCToken contract implementation.
class MockTBTCToken implements TBTCToken {
  // Implement all required methods
}

// Create mock TBTCVault contract implementation.
class MockTBTCVault implements TBTCVault {
  // Implement all required methods
}

// Create mock WalletRegistry contract implementation.
class MockWalletRegistry implements WalletRegistry {
  // Implement all required methods
}

// Create mock BitcoinClient contract implementation.
class MockBitcoinClient implements BitcoinClient {
  // Implement all required methods
}

// Create instances of the mock contracts.
const tbtcContracts: TBTCContracts = {
  bridge: new MockBridge(),
  tbtcToken: new MockTBTCToken(),
  tbtcVault: new MockTBTCVault(),
  walletRegistry: new MockWalletRegistry()
}

// Create an instance of the mock Bitcoin client.
const bitcoinClient = new MockBitcoinClient()

// Initialize the SDK.
const sdk = await TBTC.initializeCustom(tbtcContracts, bitcoinClient)

Deposit and mint

The main feature of the tBTC bridge is depositing BTC and using it to mint the ERC-20 TBTC token. This guide explains how to perform this process using the SDK.

Initialize the SDK

First, initialize the SDK, as described in the Initialize SDK guide.

Initiate the deposit

Once the SDK is initialized, you can initiate the deposit and get their Bitcoin address in the following way:

import { TBTC } from "@keep-network/tbtc-v2.ts"

// Initialized SDK.
const sdk: TBTC

// Set the P2WPKH/P2PKH Bitcoin recovery address. It can be used to recover
// deposited BTC in case something exceptional happens.
const bitcoinRecoveryAddress: string = "..."

// Initiate the deposit.
const deposit = await sdk.deposits.initiateDeposit(bitcoinRecoveryAddress)

// Take the Bitcoin deposit address. BTC must be sent here.
const bitcoinDepositAddress = await deposit.getBitcoinAddress()

Make the Bitcoin deposit transaction

The next step is doing the actual Bitcoin deposit transaction to the deposit address. There are two possible ways to do so:

  • Recommended: Use an external Bitcoin wallet

  • Non-recommended: Use the experimental SDK DepositFunding component. This code is used for testing purposes and is not safe for production use!

Initiate minting

Once the Bitcoin deposit transaction is done, minting can be initiated. This can be done using the initiateMinting method exposed by the Deposit class. This method has two modes:

  • Automatic: Automatically detect the latest funding UTXO and use it to initiate minting. This mode is the default one that should be used when a single Bitcoin deposit transaction has been made.

  • Manual: Take a funding UTXO as a parameter and use it to initiate minting. This mode can be useful when multiple Bitcoin deposit transactions target the same deposit address.

Here is an example of both:

// Initiate minting using latest funding UTXO. Returns hash of the
// initiate minting transaction. Can throw an error if there are no
// Bitcoin funding transactions targeting this deposit address. 
// In that case, consider retrying after a delay.
const txHash = await deposit.initiateMinting()
// Detect funding UTXOs manually. There can be more than one.
await fundingUTXOs = await deposit.detectFunding()

// Initiate minting using one of the funding UTXOs. Returns hash of the
// initiate minting transaction.
const txHash = await deposit.initiateMinting(fundingUTXOs[0])

Unmint and redeem

Another core feature of the tBTC bridge is unminting (burning) TBTC tokens and redeeming BTC in return. This guide explains how to perform this process using the SDK.

Initialize the SDK

First, initialize the SDK, as described in the Initialize SDK guide.

Request redemption

Once the SDK is initialized, you can unmint and request for redemption in the following way:

import { TBTC } from "@keep-network/tbtc-v2.ts"
import { BigNumber } from "ethers"

// Initialized SDK.
const sdk: TBTC

// Set the P2WPKH/P2PKH or P2WSH/P2SH Bitcoin redeemer address. This is the
// address where redeemed BTC will land.
const bitcoinRedeemerAddress: string = "..."

// Set the desired redemption amount using 1e18 precision. No need to do an 
// explicit approval on the TBTC token upfront.
const amount = BigNumber.from(5 * 1e18)

// Request redemption. This action will burn TBTC tokens and register a
// redemption request in the bridge. Returns the hash of the request redemption
// transaction and the target wallet public key that will handle the redemption.
const {
  targetChainTxHash,
  walletPublicKey
} = await sdk.redemptions.requestRedemption(bitcoinRedeemerAddress, amount)