Integrating BTC Connect

As mentioned, the process of integrating BTC Connect starts with configuration.

Before jumping in, it's important to note that this example will follow a create-react-app structure; thus, we'll repeatedly refer to the following files within this guide:

  • index.tsx, our central location for the configuration of BTC Connect.

  • App.tsx, the central component containing our core application logic (wallet connection and transaction execution).

To begin, we'll need to install several required and optional dependencies, as will be covered below.

Setup: Dependencies

BTC Connect can be integrated and utilized solely through one SDK, @particle-network/btc-connectkit. This library is responsible for handling all three steps of our example application. For the sake of programmatic simplicity, we'll be using a combination of the following libraries:

  • @particle-network/btc-connectkit, the aforementioned key library for using BTC Connect.

  • @particle-network/chains, for connecting to SatoshiVM.

  • @particle-network/aa, to simplify interaction with the associated smart account.

  • ethers, used hand-in-hand with @particle-network/aa to streamline the means of transaction execution.

To install these libraries, you'll need to run one of the two commands highlighted below at the root of your project:

yarn add @particle-network/btc-connectkit @particle-network/chains @particle-network/aa ethers


# OR


npm install @particle-network/btc-connectkit @particle-network/chains @particle-network/aa ethers


# Any other standard package manager works as well.

Part 1: Configuration and Initialization

Now that you've installed various dependencies and understand the basic structure of this example, you're ready to proceed to the first part of implementing BTC Connect: the process of configuration.

Configuring BTC Connect entails usage of the ConnectProvider component imported from @particle-network/btc-connectkit.

ConnectProvider acts as the central component for configuration, taking a variety of parameters responsible for instance authentication (API keys), wallet modal customization (we'll cover this more momentarily), and supported wallet customization (deciding the range of supported wallets within your application, such as UniSat, OKX, and so on).

Additionally, ConnectProvider should wrap the component where you intend to use BTC Connect. This is where our previously defined usage of App.tsx and index.tsx resurface.

Configuration in this example will happen within index.tsx. This file will import the App component from App.tsx, allowing ConnectProvider to wrap it and enabling the usage of BTC Connect within our application (defined within App.tsx).


With this established, you'll need to render ConnectProvider with the following parameters defined (through the options and connectors parameters):

  • projectId, clientKey, and appId. These are required values authenticating your instance of BTC Connect; they fundamentally tie your project to the Particle dashboard.

    • To retrieve these keys, open the dashboard and create a new project alongside a new application; the corresponding Project ID, Client Key, and App ID will need to be defined within their respective parameters, detailed above.

  • aaOptions, which contains accountContracts, taking:

    • BTC, an array of objects outlining chain-related configurations for the smart account. These parameters include:

      • chainIds, an array of chain IDs referring to the chains you intend to use within your application. Here, we'll be using SatoshiVMTestnet.id from @particle-network/chains.

      • version, the version of the smart account you'd like to use. For now, only 1.0.0 is supported.

  • walletOptions, settings impacting the optional embedded wallet modal responsible for facilitating interaction with the smart account derived from a user's native Bitcoin wallet.

    • This takes one parameter, visible. If set to true, the embedded wallet modal will be available through the bottom right corner of your application after the wallet connection. Otherwise, if false, the modal will be omitted, leaving the handling of the smart account solely to your application.

Outside of options, within connectors:

  • An array of wallet connectors you'd like to support within your application (such as UniSat, Bitget, or OKX). These connections can be imported from @particle-network/btc-connectkit, through:

    • OKXConnector.

    • UnisatConnector.

    • BitgetConnector.

With these parameters filled in, your index.tsx file (or its equivalent within your application) should look similar to the example below:

import React from "react"
import ReactDOM from "react-dom/client"
import {
    ConnectProvider, // Central component used for configuration
    OKXConnector, // OKX Wallet
    UnisatConnector, // Unisat Wallet
    BitgetConnector, // Bitget Wallet
} from "@particle-network/btc-connectkit"
import { SatoshiVMTestnet } from "@particle-network/chains" // Optional
import App from "./App" // The component you intend to use BTC Connect within

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
    <React.StrictMode>
        <ConnectProvider
            options={{
                projectId: process.env.REACT_APP_PROJECT_ID, // ---
                clientKey: process.env.REACT_APP_CLIENT_KEY, // Retrieved from https://dashboard.particle.network
                appId: process.env.REACT_APP_APP_ID, // ---
                aaOptions: {
                    accountContracts: {
                        BTC: [
                            {
                                chainIds: [SatoshiVMTestnet.id],
                                version: "1.0.0", // Keep as 1.0.0 for now.
                            },
                        ],
                    },
                },
                walletOptions: {
                    // Dictates whether or not the optional embedded wallet modal is shown.
                    visible: true,
                },
            }}
            // Instances of the previously imported connectors
            connectors={[new UnisatConnector(), new OKXConnector(), new BitgetConnector()]}
        >
            <App /> // The component imported above
        </ConnectProvider>
    </React.StrictMode>
)

Part 2: Initiating Wallet Connection

With BTC Connect configured and initialized, you're ready to begin building your application (or integrating BTC Connect within an existing one).

We'll be doing this within the previously mentioned App.tsx file (using the App component we wrapped within ConnectProvider).

Opening and taking a user through the wallet connection menu is quite simple and generally only takes one line of code, although it requires a preliminary setup.

First, it's important to understand how BTC Connect is used programmatically.

BTC Connect has four primary hooks that provide functions to control all core functionality. These are:

  • useETHProvider, for managing the associated smart account on SatoshiVM.

  • useBTCProvider, to receive the user's Bitcoin address and execute native BTC transactions.

  • useConnectModal, for initiating a wallet connection.

  • useAccounts, for retrieving the user's addresses.

We'll walk through the process of leveraging useETHProvider (alongside @particle-network/aa and ethers) and useBTCProvider in part 3. For now, let's focus on useConnectModal.

After importing useConnectModal from @particle-network/btc-connectkit, we'll need to define the openConnectModal function (and optionally, the disconnect function). An example of this has been included below:

import { useETHProvider, useBTCProvider, useConnectModal } from '@particle-network/btc-connectkit';


const App = () => {
  ...

  const { openConnectModal, disconnect } = useConnectModal();
  const { accounts } = useBTCProvider(); // We'll be using this in a moment.

  ...
}

openConnectModal is the sole function responsible for both initiating and handling end-to-end wallet connections. In this example, we'll wrap openConnectModal within a function, handleLoginto ensure its execution only happens conditionally.

handleLogin will only execute openConnectModal if the user has yet to connect their wallet. This is indicated by the length of the accounts array from useBTCProvider, which is populated upon wallet connection.

Upon calling handleLogin, if the user has yet to connect, a generalized connection interface will be displayed, allowing a user to choose between the wallets previously defined within the connectors parameter through ConnectProvider. After choosing and connecting a specific wallet, they'll be ready to use your application either through their native Bitcoin account or the newly assigned smart account (on SatoshiVM).

handleLogin, or an equivalent function within your application, may look like the below example.

const handleLogin = () => {
    if (!accounts.length) {
        openConnectModal() // Opens a generalized connection interface
    }
}

Part 3: Transaction Execution

Taking a few steps back, let's revisit the hooks discussed in part 2.

As mentioned, the useETHProvider and useBTCProvider hooks are used to control their associated accounts (smart account and native Bitcoin account).

We'll begin by focusing on useETHProvider, using it to send a gasless burn transaction of 0.0001 BTC.

EVM Transaction

useETHProvider exposes a few objects, but the most important one among them is smartAccount. This is the sole object for controlling the smart account generated after the wallet connection.

smartAccount can either be used independently, through methods like smartAccount.sendTransaction, or it can be used alongside @particle-network/aa to define an associated EIP-1193 provider to be used within an instance of Ethers.

The latter is what we’ll use in this example.

Specifically, you'll need to import the following from @particle-network/aa:

  • AAWrapProvider, for constructing the custom EIP-1193 provider.

  • SendTransactionMode, to designate the fee payment method for transactions sent through the Ethers instance.

This, in tandem with smartAccount, a custom Ethers object, can be constructed through a flow similar to the following:

import React, { useState, useEffect } from 'react';
import { useETHProvider, useBTCProvider, useConnectModal, useConnector } from '@particle-network/btc-connectkit';
import { AAWrapProvider, SendTransactionMode } from '@particle-network/aa';
import { ethers } from 'ethers';


const App = () => {
  const { smartAccount } = useETHProvider();

  ...

  const customProvider = new ethers.providers.Web3Provider(new AAWrapProvider(smartAccount, SendTransactionMode.Gasless), "any");

  ...
}

Therefore, this object, customProvider, will allow for the construction and execution of transactions through standard Ethers syntax and structure while directly routing signatures through BTC Connect.

By including SendTransactionMode.Gasless within the construction of AAWrapProvider, we'll be requesting gas sponsorship on every transaction sent through customProvider. Since we’re using the SatoshiVM Testnet, all transactions will be automatically sponsored (gasless). However, if this were on Mainnet, you'd need to deposit USDT to the Paymaster within the Particle dashboard.

:::info

This method of building a custom EIP-1193 provider with smartAccount to use BTC Connect through a standard Web3 library is not exclusive to Ethers. AAWrapProvider can be used within Web3.js, viem, or any other library with EIP-1193 compatibility.

:::

Using customProvider to send a transaction is straightforward, following a typical Ethers flow. As such, developers familiar with Ethers might be used to this.

In this example, we'll be executing a burn transaction of 0.0001 BTC within executeTxEvm. This function will construct a simple tx object containing fields such as to and value.

Using a signer object retrieved through customProvider.getSigner(), this raw tx object can be used to execute a complete transaction (UserOperation).

To do this, we'll call signer.sendTransaction(tx), which will prompt the user for confirmation (signature) through the Bitcoin wallet they connected previously. Upon confirmation, the transaction will be executed on SatoshiVM.

Below, you'll find an example for what a function of this nature may look like.

const executeTxEvm = async () => {
    const signer = customProvider.getSigner()

    const tx = {
        to: "0x000000000000000000000000000000000000dEaD",
        value: ethers.utils.parseEther("0.01"),
        data: "0x",
    }

    const txResponse = await signer.sendTransaction(tx)
    const txReceipt = await txResponse.wait()

    return txReceipt.transactionHash
}

BTC Transaction

Alternatively, the same wallet interface can be used to send a native (L1) Bitcoin transaction, using the account attached to the previously connected wallet.

We'll do this through the sendBitcoin function from the useBTCProvider hook (akin to smartAccount from useETHProvider, but solely for executing a Bitcoin transaction).

sendBitcoin can be defined through useBTCProvider using syntax adjacent to the example below.

import { useETHProvider, useBTCProvider, useConnectModal } from '@particle-network/btc-connectkit';


const App = () => {
  ...

  const { sendBitcoin, accounts } = useBTCProvider();

  ...
}

Executing this function will be as simple as passing in two parameters (this is all that's needed for a P2P transaction on Bitcoin). These parameters are:

  • toAddress, the recipient of the transaction. In this example, we'll define the recipient as accounts[0], which will send the BTC back to ourselves, just for the sake of demonstration.

  • satoshis, the value of the transaction denominated in satoshis.

  • Optionally, the options parameter can be used to define:

    • feeRate, for making manual adjustments to the transaction's gas fee.

We'll execute sendBitcoin through executeTxBtc, which should look similar to the following snippet:

const executeTxBtc = async () => {
    return await sendBitcoin(accounts[0], 1)
}

Upon calling executeTxBtc, alike executeTxEvm, the user will be prompted to confirm and sign the transaction. After this, it'll be sent to Bitcoin for execution.

Last updated