CBTC Minting and Burning
⚠️ API Disclaimer: CBTC APIs are subject to change. There is no formal versioning policy today. Breaking changes are communicated via the site changelog.
Overview: The Full BTC to CBTC Lifecycle
This guide covers the complete lifecycle of converting Bitcoin to CBTC and back: minting (BTC to CBTC on Canton) and burning (CBTC back to BTC). For a quick end-to-end walkthrough, see the CBTC Quick Start. This guide goes deeper into each step, covering edge cases, error handling, and recovery patterns for production integrations.
🛠️ Prerequisite: install the SDK. The Rust examples on this page use the
cbtc-libSDK. See SDK Setup and Installation for installingcbtc-libandcanton-lib, uploading the CBTC DAR files, and configuring your environment.
Key facts:
Exchange rate: 1 BTC = 1 CBTC, always
Confirmations required: 6 Bitcoin block confirmations (~60 minutes)
Processing time: Additional 60-120 seconds after confirmations for Attestor verification
Wallet requirement: Taproot-compatible Bitcoin wallet (P2TR addresses)
Transaction limits: 0.0001 BTC minimum, 5 BTC maximum by default - set per account and adjustable on request (see below)
Minter credential: Required before you can create a deposit or withdraw account (see below)
Transaction Limits
Mint and burn amounts are bounded by per-account limits. The defaults are:
Minimum
0.0001 BTC
Maximum
5 BTC
💡 Limits are adjustable. These are defaults, not fixed protocol constraints. If your integration needs a different range, contact sales@bitsafe.finance to have your account limits modified.
Read limits from the account, do not hardcode them
Because limits are set per account and can be changed on request, read them at runtime rather than hardcoding the defaults. They are carried on the Deposit Account (for minting) and the Withdraw Account (for burning), and are returned by the deposit account status call:
Both fields are optional. None means that bound is not enforced for the account:
Validate before submitting
cbtc::mint_redeem::models::check_limits validates an amount against a set of limits locally, so an out-of-range request fails in your code instead of being rejected downstream:
It returns a descriptive error - "Withdraw amount 0.00001 is below minimum 0.0001" or "... exceeds maximum 5" - and succeeds when no limits are set.
Prerequisite: Obtain a Minter Credential
⚠️ Minting and burning require a Minter credential. This is a hard requirement, not an optional step. Creating a deposit account or a withdraw account will fail without one. Transferring, receiving, and holding CBTC do not require a credential — only minting and burning do.
A Minter credential is a Daml contract issued to your Canton party by the CBTC registrar. It carries a claim with the property hasCBTCRole and the value Minter, and you pass its contract ID into the account-creation calls.
How to request one
Minter credentials are issued as part of commercial onboarding. To request one, contact the BitSafe team at sales@bitsafe.finance with your Canton Party ID and target environment (testnet or mainnet).
Check whether you already hold one
Once the registrar has issued your credential, use the cbtc::credentials module to find it. A credential is a Minter credential if any of its claims has property == "hasCBTCRole" and value == "Minter":
Accept a pending credential offer
The registrar issues the credential as an offer that your party must accept before it becomes active. If list_credentials returns nothing, check for a pending offer and accept it:
ℹ️ Accepting a credential is permanent. It creates a persistent on-ledger contract with no archive choice, so a credential cannot be un-accepted. Accept only the offer you actually intend to use.
A complete runnable version of this flow is in the library's credentials example.
How to Mint CBTC: Deposit Bitcoin and Receive Wrapped BTC on Canton
How It Works
Step-by-Step
Step 1: Authenticate
Obtain a JWT token from your OIDC provider (Keycloak is officially supported). See the Authentication Guide for setup details.
Step 2: Create a Deposit Account
A Deposit Account is required before you can generate deposit addresses. This call requires the Minter credential contract IDs obtained in the prerequisite step above.
Using cbtc-lib (Rust):
Using Canton API (curl):
You can fetch the CBTCDepositAccountRules from the BitSafe API's /cbtc/v1/account-contract-rules endpoint
⚠️ A Minter credential is required. The
CreateDepositAccountchoice takes your Minter credential contract IDs as an argument. See Prerequisite: Obtain a Minter Credential above. Requests without a valid credential are rejected.
Note: The submit-and-wait-for-transaction-tree endpoint is deprecated in Canton 3.5 but remains functional on 3.5.1. Consider migrating to submit-and-wait-for-transaction for new integrations.
Step 3: Generate a Bitcoin Deposit Address
Each deposit address is unique to your account and is a standard Taproot (P2TR) address.
Using cbtc-lib (Rust):
Step 4: Send Bitcoin
Send the exact amount of BTC you want to mint as CBTC to the generated Taproot address from your Bitcoin wallet.
Step 5: Wait for Confirmations
The Attestor network automatically monitors the Bitcoin network. Once your transaction reaches 6 confirmations (~60 minutes), it transitions to the processing state.
You can poll for deposit status:
Using cbtc-lib (Rust):
Step 6: Attestor Verification
This step is fully automated. The Attestor network:
Independently verifies the Bitcoin transaction has 6+ confirmations
Each Attestor submits a
ConfirmDepositActionto the Canton governance moduleOnce the required threshold of confirmations is reached, the Coordinator executes the mint No action is required from your application during this step.
Step 7: CBTC Available
Your CBTC is minted and available in your Canton party. Check your balance:
Using cbtc-lib (Rust):
How to Burn CBTC: Redeem Wrapped Bitcoin for Native BTC
How It Works
Step-by-Step
Step 1: Initiate a Burn
First, create a WithdrawAccount with your destination BTC address. The destination address is stored on the WithdrawAccount and can be updated later. Then submit the withdrawal against that account. Like deposit account creation, this requires your Minter credential contract IDs.
Using cbtc-lib (Rust):
ℹ️ Amounts are
DamlDecimal. Build them withcbtc::DamlDecimal::parse, which validates the value against Daml's decimal precision rules up front, so an unrepresentable amount fails locally instead of being rejected by the ledger. This applies to transfer and allocation amounts too.
Step 2: Attestor Verification and Signing
The Attestor network:
Verifies the burn request on Canton
Constructs the Bitcoin withdrawal transaction
Coordinates FROST threshold signing across Attestors
Once the signing threshold is met, broadcasts the signed transaction to the Bitcoin network This step is fully automated. No action required.
Step 3: Bitcoin Delivery
After the signed transaction is broadcast, wait for 6 Bitcoin confirmations. Your BTC will arrive at the specified destination address.
Error Handling and Recovery Patterns for CBTC Integrations
🔧 Error handling is critical for production integrations. The CBTC system includes built-in resilience, but your application should handle these scenarios gracefully.
Failed Broadcast
The system includes automatic retry logic. If a Bitcoin transaction fails to broadcast initially, the Coordinator detects the failure during subsequent periodic checks (every 60-120 seconds) and rebroadcasts using stored transaction data.
What your app should do: Monitor withdrawal status. If status remains in broadcasting for more than 10 minutes, log an alert for investigation.
Insufficient Confirmations
If a deposit stalls below 6 confirmations (e.g., due to Bitcoin network congestion), the system simply waits. There is no timeout.
What your app should do: Display the current confirmation count to the user. Consider showing an estimated time based on current Bitcoin block times.
Idempotency
Each withdrawal generates a unique transaction ID that prevents accidental double-spending, even if network issues cause retry attempts. The system is designed to be idempotent.
What your app should do: Store the withdrawal request ID and use it for status checks rather than initiating duplicate requests.
Attestor Timeout
If governance approval is delayed (e.g., some Attestors are temporarily offline), the system continues to collect approvals. As long as the threshold can eventually be met, the operation will complete.
What your app should do: If a mint or burn is pending for more than 2 hours, escalate to BitSafe support.
Partial Failure
If some Attestors approve but the threshold is not reached (e.g., too many Attestors offline simultaneously), the operation will remain pending until the threshold is met or the situation is resolved.
What your app should do: Alert your operations team. Contact support@bitsafe.finance.
CBTC UTXO Management: Consolidation and Best Practices
⚠️ Important for high-volume integrations. Each CBTC transfer creates UTXOs. Canton recommends a maximum of 10 UTXOs per party. Exceeding this causes increased load and fees on your node.
The cbtc-lib Rust library provides functions for managing UTXOs:
Best practices:
Monitor UTXO count per party and consolidate proactively
Batch transfers where possible to minimise UTXO creation
If creating many parties, use the Ledger API directly (not wallet UI/API) - see Canton docs
Escalation Path
Mint pending > 2 hours
Check Bitcoin confirmations first. If 6+ confirmations reached, contact BitSafe
Burn pending > 2 hours
Contact BitSafe engineering
Unexpected error from API
Retry with exponential backoff. If persistent, contact BitSafe
UTXO-related issues
Use cbtc-lib consolidation functions. If unresolved, contact BitSafe
Support channels:
Email: support@bitsafe.finance
Last updated