Depositing: Pegging BTC into sBTC
This guides shows how you can integrate the deposit (peg-in) flow from your front-end app to allow users to peg BTC into sBTC on the Stacks network. For more information about sBTC and an explainer of its architecture, check out the general sBTC section here in the Learn category.
Breakdown of the deposit (peg-in) flow
Create Deposit (Bitcoin) Transaction:
Structure a Bitcoin transaction to send funds to the group of signers.
Use a specialized format that includes:
Deposit Script: Identifies which Stacks address the sBTC will be minted to and what the maximum fee (in satoshis) the signers may take in exchange for minting.
Reclaim Script: Allows the sender to reclaim their funds if the transaction is not processed by the signers.
Sign and Broadcast the Transaction:
Sign the transaction with the sender's private key.
Broadcast the transaction to the Bitcoin network (Bitcoin Regtest for Stacks Testnet).
Notify the sBTC API (Emily):
Inform the API about the transaction by submitting its details. This step ensures that the signers are aware of the deposit and can track it.
Processing by Signers: (no action required)
The signers retrieve and verify the deposit transaction from the Bitcoin blockchain.
Once verified, the signers mint the equivalent amount of sBTC on the Stacks network.
Receive sBTC (Stacks): (no action required)
The minted sBTC is sent to the depositor's designated Stacks address, completing the deposit process.
sBTC is SIP-010 compatible and will show up in Stacks wallets and explorers.
In this guide you'll touch on some of the steps above but its much simpler than you'd expect. Using the sbtc and @stacks/connect libraries, putting together the peg-in process from BTC into sBTC will simply involve the following steps:
Building the sBTC deposit address
Invoking the user's wallet to sign and broadcast the bitcoin transaction
Notifying the sBTC signers
Confirm user's sBTC balance
Building the sBTC deposit address
You're not directly sending bitcoin to the public sBTC Signers' bitcoin address, but rather sending to a custom P2TR address where both the user and sBTC Signers have control over. This custom P2TR address is special because it contains tapscripts that specify which parties are able to unlock the UTXOs via a script path spend.
The construction of these tapscripts is what ultimately generates the custom P2TR address that the user will be sending their UTXOs to. Constructing tapscripts, or bitcoin scripts in general, are complex and tricky. The sbtc library provides useful methods for abstracting away the complexities of working with taproot related functions.
import { buildSbtcDepositAddress, MAINNET, SbtcApiClientMainnet } from 'sbtc';
// Initialize the sBTC API client based on network
const client = new SbtcApiClientMainnet();
// Build the deposit address with metadata
const deposit = buildSbtcDepositAddress({
stacksAddress: 'SP14ZYP25NW67XZQWMCDQCGH9S178JT78QJYE6K37', // the address to send/mint the sBTC to
signersPublicKey: await client.fetchSignersPublicKey(), // the aggregated public key of the signers
reclaimLockTime: 700, // default value is 950
reclaimPublicKey: btcPubKey.value, // public key for reclaiming failed deposits
network: MAINNET,
maxSignerFee: 4000 // optional property, default value is 80,000 sats
});
// `deposit.address` is the deposit address (send funds here, aka the deposit address as an output)The buildSbtcDepositAddress will return with a schema of:
deposit {
depositScript: string;
reclaimScript: string;
trOut: P2TROut;
address: string; // the custom P2TR address to deposit bitcoin to
}Sign and broadcast the bitcoin transaction
Invoke the user's Stacks wallet to sign and broadcast the deposit bitcoin transaction.
The string literal sendTransfer method will invoke the user's wallet to construct a bitcoin transaction. Be certain that the user's Stacks-supported wallet also supports sendTransfer as it is part of WBIP005 for default Bitcoin methods.
import { request } from '@stacks/connect';
const result = await request('sendTransfer', {
recipients: [
{
address: deposit.address,
amount: 100_000,
},
],
})Notify the sBTC Signers
Immediately after the deposit bitcoin transaction is broadcasted, fetch the transaction hex and notify the sBTC Signers via the Emily API.
You'll want to wait until the transaction hits the bitcoin mempool before fetching the transaction hex. Usually this is within seconds.
// below should be delayed until txid appears in the bitcoin mempool
const transaction = await client.fetchTxHex(result.txid);
// 3. NOTIFY THE SIGNERS
let response = await client.notifySbtc({ transaction, ...deposit });
console.log('Notify response:', response);The notifySbtc method will return with a schema of:
Confirm user's sBTC balance
The user should expect to receive their newly minted sBTC in about 20 minutes, or within 1 to 2 confirmations on the Bitcoin chain. Poll the user's specified stacksAddress to check if they've received sBTC and display that to the user on the front-end.
The API client comes with a fetchSbtcBalance method that can help with this:
const balance = await client.fetchSbtcBalance('SP3FGQ8Z7JY9BWYZ5WM53E0M9NK7WHJF0691NZ159');
// 1000000n (in micro-sBTC)You could also fetch the deposit data from the API client. This will return data pertaining to the 3 total transactions that make up the entire deposit (peg-in) flow:
And that's all to it. You've successfully allowed your app to handle incoming BTC to be pegged into sBTC onto the Stacks network.
[Additional Insights]
What scripts make up the custom P2TR bitcoin address?
As mentioned above, you're not directly sending bitcoin to the public sBTC Signers' bitcoin address, but rather sending to a custom P2TR address where both the user and sBTC Signers have control over. Besides the default key path spend, this custom P2TR address also contains 2 sets of scripts:
Deposit script
The deposit script is used by the sBTC Signers to "sweep" the deposited UTXO into their aggregate bitcoin address used to hold the entire balance of deposited bitcoin. So in a way they are consolidating all UTXOs into one single UTXO.
The construction of the deposit script:
Reclaim script
The reclaim script allows the initial depositor to reclaim their UTXOs.
The construction of the reclaim script:
Behind the scenes, these two script construction methods are being abstracted away by buildSbtcDepositAddress which you've implemented on the front-end.
How are fees dealt with?
During deposits
The maxSignerFee refers to the fee in the bitcoin transaction sweeping funds into the consolidated UTXO locked exclusively by sBTC Signers' aggregate address. Depending on network congestion, specify a custom fee your users would be willing to spend. The default value will be 80,000 sats. The user's responsibility of the actual fee spent (for the sweep transaction) is actually deducted from the amount of sBTC that will be minted.
During withdrawals
The fees specified in max-fee of the function initiate-withdrawal-request of the .sbtc-withdrawal contract are referring to the fees paid of the bitcoin withdrawal transaction.
How to estimate how much in fees one should spend?
If you want to estimate how much one would expect to be charged in fees, you'd have to estimate the size of the transaction (vbytes) and the current network's fee rate. Below are some estimations you could use as a benchmark:
For deposits: ~250 vbytes times the prevailing sats per vbyte fee rate For withdrawals: ~170 vbtytes times the prevailing sats per vbyte rate
And although many deposits and withdrawals can be combined, these values should be the maximum that a user will be charged regardless of how many other deposits or withdrawals are being serviced in a single transaction by the Signers. Meaning when more than one user's request is included in a sweep transaction on the L1, the users share the fees in proportion to their deposit or withdrawal request's actual weight on the L1.
Last updated
Was this helpful?
