Integrate Web3Auth with the Bitcoin Blockchain
When using the Web3Auth Web SDK for non-EVM chains like Bitcoin, you get a standard provider that allows you to access the user's private key. With this private key, you can use the appropriate Bitcoin blockchain libraries to perform various actions such as retrieving the user's account information, fetching their balance, signing transactions, and sending transactions. We've highlighted a few methods here to help you get started quickly.
This documentation is for the Bitcoin Taproot Integration
Installation
- npm
- Yarn
- pnpm
npm install --save bitcoinjs-lib ecpair axios
yarn add bitcoinjs-lib ecpair axios
pnpm add bitcoinjs-lib ecpair axios
Initializing Provider
Getting the chainConfig
- Mainnet
- Testnet
const chainConfig = {
chainNamespace: CHAIN_NAMESPACES.OTHER,
chainId: "Bitcoin", //
rpcTarget: "https://api.blockcypher.com/v1/btc/main", // Avoid using public rpcTarget in production.
displayName: "Bitcoin Mainnet",
blockExplorerUrl: "https://blockstream.info/",
ticker: "BTC",
tickerName: "Bitcoin",
};
const chainConfig = {
chainNamespace: CHAIN_NAMESPACES.OTHER,
chainId: "Bitcoin", //
rpcTarget: "https://api.blockcypher.com/v1/btc/test3", // Avoid using public rpcTarget in production.
displayName: "Bitcoin Testnet",
blockExplorerUrl: "https://blockstream.info/testnet/",
ticker: "tBTC",
tickerName: "Bitcoin Testnet",
};
Initializing and instantiating the Web3Auth SDK
- PnP Modal SDK
- PnP NoModal SDK
- CoreKit SFA Web SDK
import { Web3Auth } from "@web3auth/modal";
import { CommonPrivateKeyProvider } from "@web3auth/base-provider";
import { WEB3AUTH_NETWORK, CHAIN_NAMESPACES } from "@web3auth/base";
const privateKeyProvider = new CommonPrivateKeyProvider({
config: { chainConfig: chainConfig },
});
const web3auth = new Web3Auth({
// Get it from Web3Auth Dashboard
clientId,
web3AuthNetwork: WEB3AUTH_NETWORK.SAPPHIRE_MAINNET,
privateKeyProvider: privateKeyProvider,
});
import { Web3AuthNoModal } from "@web3auth/no-modal";
import { AuthAdapter } from "@web3auth/auth-adapter";
import { CommonPrivateKeyProvider } from "@web3auth/base-provider";
import { WEB3AUTH_NETWORK, CHAIN_NAMESPACES } from "@web3auth/base";
const privateKeyProvider = new CommonPrivateKeyProvider({
config: { chainConfig },
});
const web3auth = new Web3AuthNoModal({
clientId, // Get it from Web3Auth Dashboard
web3AuthNetwork: WEB3AUTH_NETWORK.SAPPHIRE_MAINNET,
});
const authAdapter = new AuthAdapter();
web3auth.configureAdapter(authAdapter);
import { Web3Auth } from "@web3auth/single-factor-auth";
import { CommonPrivateKeyProvider } from "@web3auth/base-provider";
import { WEB3AUTH_NETWORK, CHAIN_NAMESPACES } from "@web3auth/base";
const web3auth = new Web3Auth({
clientId, // Get your Client ID from Web3Auth Dashboard
web3AuthNetwork: WEB3AUTH_NETWORK.SAPPHIRE_MAINNET,
});
const privateKeyProvider = new CommonPrivateKeyProvider({
config: { chainConfig },
});
Getting the Web3Auth provider
After initializing Web3Auth, the next step is to initialize the provider and use it for your operations.
- PnP Modal SDK
- PnP NoModal SDK
- CoreKit SFA Web SDK
// Initialize for PnP Modal SDK
await web3auth.initModal();
// Trigger the login
await web3auth.connect();
// Get the provider
const provider = web3auth.provider;
// Continue using the `provider`
// Initialize for PnP NoModal SDK
await web3auth.init();
// Trigger the login
await web3auth.connectTo("auth");
// Get the provider
const provider = web3auth.provider;
// Continue using the `provider`
// Initialize for CoreKit SFA Web SDK
await web3auth.init();
// Trigger the login
await web3auth.connect();
// Get the provider
const provider = web3auth.provider;
// Continue using the `provider`
After logging in, the Web3Auth instance will provide you with information regarding the user that is logged in. This information is obtained directly from the JWT token and is not stored by Web3Auth. Therefore, this information can only be accessed through social logins after the user has logged into your application.
const user = await web3auth.getUserInfo(); // web3auth instance
Get Bitcoin Account and KeyPair
Once a user logs in, the Web3Auth SDK returns a provider. Since Web3Auth doesn't have a native provider for Bitcoin, we need to directly use the private key to make the KeyPair using the necessary Bitcoin libraries.
Using the function, web3auth.provider.request({method: "private_key"})
from Web3Auth provider, the
application can have access to the user's private key. This private key can be used to derive the
Bitcoin KeyPair using the bitcoinjs-lib
library.
import { IProvider } from "@web3auth/base";
import ecc from "@bitcoinerlab/secp256k1";
import ECPairFactory from "ecpair";
import { Psbt, networks, payments, crypto, initEccLib } from "bitcoinjs-lib";
import axios from "axios";
/*
Use code from the above Initializing Provider here
*/
// web3authProvider is web3auth.provider from above
const privateKey = await web3authProvider.request({ method: "private_key" });
const keyPair = ECPair.fromPrivateKey(Buffer.from(privateKey, "hex"));
const bufPubKey = keyPair.publicKey;
const xOnlyPubKey = bufPubKey.subarray(1, 33);
const tweakedChildNode = keyPair.tweak(crypto.taggedHash("TapTweak", xOnlyPubKey));
const { address, output } = payments.p2tr({
pubkey: Buffer.from(tweakedChildNode.publicKey.subarray(1, 33)),
network,
});
The code above uses the privateKey
to create a keyPair
using the ECPair
class from the
bitcoinjs-lib
library. The keyPair
is then used to derive the xOnlyPubKey
, and
tweakedChildNode
using the tweak
method. The address
and output
are then generated using the
payments.p2tr
method.
Key Points:
-
keyPair
: This is the Bitcoin key pair derived from the provided private key from Web3Auth using theECPair
class. The key pair consists of the private key and the public key. The private key is used to sign transactions, while the public key is used to generate the Bitcoin address. -
xOnlyPubKey
: This represents the x-coordinate of the public key, excluding the prefix byte. Taproot uses Schnorr signatures, which require only the x-coordinate of the public key. This x-only format is more efficient and is a key aspect of the Taproot design. -
tweakedChildNode
: This is the resulting key after tweaking the original public key. Taproot involves tweaking the public key using a tagged hash, which modifies the public key in a way that remains cryptographically secure but incorporates additional data or functionality. -
address
: This is the Bitcoin Taproot address derived from the provided public key. It is used to receive Bitcoin. -
output
: This is the output script associated with the address. In Bitcoin transactions, an output script (also known as a scriptPubKey) defines the conditions that must be met to spend the funds sent to an address. For Taproot addresses, the output script enforces the spending conditions defined by the Taproot upgrade.
Get Balance
/*
Use `address` from above
*/
const response = await axios.get(`https://blockstream.info/testnet/api/address/${address}/utxo`);
const utxos = response.data.filter(
(utxo: { status: { confirmed: boolean } }) => utxo.status.confirmed,
);
const balance = utxos.reduce((acc: any, utxo: { value: any }) => acc + utxo.value, 0);
console.log("Bitcoin Balance: ", balance);
The code above fetches the UTXOs for the given address
and filters out the unconfirmed UTXOs. The
balance is then calculated by summing the value of the UTXOs.
Send Transaction
/*
Use `address`, `output`, `tweakedChildNode`, `xOnlyPubKey`, `utxos` from above.
*/
const utxo = utxos[0];
const feeResponse = await axios.get("https://blockstream.info/testnet/api/fee-estimates");
// For mainnet use https://blockstream.info/api/fee-estimates
const maxFee = Math.max(...(Object.values(feeResponse.data) as number[]));
const fee = maxFee * 1.2;
if (amount <= fee) {
const errorMsg = `Insufficient funds: ${amount} <= ${fee}`;
uiConsole(errorMsg);
throw new Error(errorMsg);
}
const sendAmount = amount - Math.floor(fee);
const psbt = new Psbt({ network })
.addInput({
hash: utxo.txid,
index: utxo.vout,
witnessUtxo: { value: utxo.value, script: output! },
tapInternalKey: xOnlyPubKey,
})
.addOutput({
value: sendAmount,
address: "tb1ph9cxmts2r8z56mfzyhem74pep0kfz2k0pc56uhujzx0c3v2rrgssx8zc5q",
});
psbt.signInput(0, tweakedChildNode);
psbt.finalizeAllInputs();
const txHex = psbt.extractTransaction().toHex();
try {
const response = await axios.post(`https://blockstream.info/testnet/api/tx`, txHex);
// For mainnet use https://blockstream.info/api/tx
console.log("Transaction sent successfully:", response.data);
return response.data;
} catch (error) {
console.error("Error sending transaction:", error);
throw error;
}
The code above sends a transaction to the Bitcoin blockchain. The code fetches the fee estimates
from the Bitcoin blockchain and calculates the fee for the transaction. The code then creates a PSBT
object and adds the input and output to the PSBT object. The input is signed using the
tweakedChildNode
and the PSBT object is finalized. The transaction is then sent to the Bitcoin
blockchain.
Example
You can find a complete example of the Bitcoin Taproot integration in this GitHub Repo.
Live Demo: https://sfa-web-bitcoin-example.vercel.app