For the complete documentation index, see llms.txt. This page is also available as Markdown.

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-lib SDK. See SDK Setup and Installation for installing cbtc-lib and canton-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:

Limit
Default

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 CreateDepositAccount choice 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:

  1. Independently verifies the Bitcoin transaction has 6+ confirmations

  2. Each Attestor submits a ConfirmDepositAction to the Canton governance module

  3. Once 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 with cbtc::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:

  1. Verifies the burn request on Canton

  2. Constructs the Bitcoin withdrawal transaction

  3. Coordinates FROST threshold signing across Attestors

  4. 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

Situation
Action

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:


Last updated