sBTC integration with Clarinet
In this guide, you'll learn how to interact with the mainnet sBTC contract in your Clarinet project.
What you'll learn
Prerequisites
- Clarinet 2.15.0 or later required for automatic sBTC integration.
Quickstart
Add sBTC to your project
Add the sBTC smart contracts to your Clarinet project requirements:
$clarinet requirements add SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-deposit
This command adds three essential contracts:
sbtc-token
- The core SIP-010 token contractsbtc-registry
- Registry for managing sBTC configurationsbtc-deposit
- Handles deposit and withdrawal operations
When Clarinet detects these contracts, it automatically funds your test wallets with sBTC for testing.
Create an sBTC-enabled contract
Build a simple NFT marketplace that accepts sBTC payments:
;; Define NFT(define-non-fungible-token marketplace-nft uint);; Price in sats (smallest sBTC unit)(define-data-var mint-price uint u100)(define-data-var next-id uint u0);; Mint NFT with sBTC payment(define-public (mint-with-sbtc)(begin;; Transfer sBTC from buyer to contract(try! (contract-call? 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token transfer(var-get mint-price)tx-sender(as-contract tx-sender)none));; Mint the NFT(try! (nft-mint? marketplace-nft (var-get next-id) tx-sender));; Increment ID for next mint(ok (var-set next-id (+ (var-get next-id) u1)))));; Check sBTC balance(define-read-only (get-sbtc-balance (owner principal))(contract-call? 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token get-balance owner))
Test in Clarinet console
Clarinet automatically funds test wallets with sBTC. Test your contract:
$clarinet console
Check wallet balances and mint an NFT:
;; Check deployer's sBTC balance (auto-funded)(contract-call? .nft-marketplace get-sbtc-balance tx-sender);; Mint NFT with wallet_1 (also auto-funded)(contract-call? .nft-marketplace mint-with-sbtc);; Verify NFT ownership(nft-get-owner? .nft-marketplace marketplace-nft u0)
Write unit tests
Test sBTC functionality in your TypeScript tests:
import { describe, expect, it } from "vitest";import { Cl } from "@stacks/transactions";describe("NFT Marketplace", () => {it("mints NFT with sBTC payment", () => {const mintPrice = 100;// Get initial sBTC balanceconst initialBalance = simnet.callReadOnlyFn("nft-marketplace","get-sbtc-balance",[Cl.standardPrincipal(accounts.get("wallet_1")!.address)],accounts.get("wallet_1")!.address);// Mint NFTconst mintResult = simnet.callPublicFn("nft-marketplace","mint-with-sbtc",[],accounts.get("wallet_1")!.address);expect(mintResult.result).toBeOk();// Verify sBTC was transferredconst finalBalance = simnet.callReadOnlyFn("nft-marketplace","get-sbtc-balance",[Cl.standardPrincipal(accounts.get("wallet_1")!.address)],accounts.get("wallet_1")!.address);expect(Number(Cl.parse(finalBalance.result))).toBeLessThan(Number(Cl.parse(initialBalance.result)));});});
Deploy to testnet
On testnet, Clarinet automatically remaps to the official Hiro sBTC contracts:
$clarinet deployments generate --testnet
Your deployment plan shows the remapped addresses:
---id: 0name: Testnet deploymentnetwork: testnetstacks-node: "https://api.testnet.hiro.so"bitcoin-node: "http://blockstream.info"plan:batches:- id: 0transactions:- requirement-publish:contract-id: ST1F7QA2MDF17S807EPA36TSS8AMEFY4KA9TVGWXT.sbtc-tokenremap-sender: SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4remap-principals:SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4: ST1F7QA2MDF17S807EPA36TSS8AMEFY4KA9TVGWXTcost: 50000path: "./.cache/requirements/SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token.clar"
Deploy to testnet:
$clarinet deployments apply -p deployments/default.testnet-plan.yaml
Common patterns
Working with sBTC addresses
Clarinet handles sBTC contract address mapping across networks:
Network | sBTC Contract Address |
---|---|
Simnet/Devnet | SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token |
Testnet | ST1F7QA2MDF17S807EPA36TSS8AMEFY4KA9TVGWXT.sbtc-token |
Mainnet | Contract address remains unchanged |
Your contract code always references the simnet address. Clarinet automatically remaps during deployment.
Manual sBTC minting in unit tests
While Clarinet 2.15.0+ automatically funds wallets with sBTC in devnet, you may need to manually mint sBTC in unit tests for specific scenarios:
Minting sBTC using the deployer address
The sBTC token contract allows the deployer (multisig) address to mint tokens. Use this approach in your tests:
import { describe, expect, it } from "vitest";import { Cl } from "@stacks/transactions";describe("Manual sBTC minting", () => {it("mints sBTC to custom addresses", () => {// The sBTC multisig address that can mintconst sbtcDeployer = "SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4";const customWallet = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM";// Mint 1000 sats to custom walletconst mintResult = simnet.callPublicFn("SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token","mint",[Cl.uint(1000), // amount in satsCl.principal(customWallet) // recipient],sbtcDeployer // sender must be deployer);expect(mintResult.result).toBeOk(Cl.bool(true));// Verify balanceconst balance = simnet.callReadOnlyFn("SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token","get-balance",[Cl.principal(customWallet)],customWallet);expect(balance.result).toBeOk(Cl.uint(1000));});});
Testing with mainnet execution simulation
When using mainnet execution simulation, you can mint sBTC using the actual mainnet multisig:
const mainnetMultisig = "SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4";const mainnetWallet = "SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR";// Mint sBTC to any mainnet addresssimnet.callPublicFn("SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token","mint",[Cl.uint(100000), Cl.principal(mainnetWallet)],mainnetMultisig);
This approach is useful for:
- Testing specific sBTC amounts
- Simulating different wallet balances
- Testing edge cases with precise token amounts
- Integration testing with mainnet contracts