CBTC Quick Start
⚠️ API Disclaimer. CBTC APIs have no formal versioning policy today. All endpoints and library interfaces described in this guide are subject to change. Breaking changes are communicated via the changelog. This disclaimer will be updated once a formal versioning and stability policy is established.
This step-by-step guide walks you through minting your first CBTC (wrapped Bitcoin) on the Canton Network. You will authenticate with Keycloak, create a deposit account, send BTC to a Taproot address, and receive 1:1 backed CBTC on your Canton participant node. The full process takes about 15 minutes of active work plus ~60 minutes of Bitcoin confirmation time.
🎯 What you will accomplish
- Authenticate to the Canton Network via Keycloak
- Create a CBTC deposit account
- Obtain a Bitcoin deposit address
- Send BTC and wait for confirmation
- Verify your CBTC balance
- Send CBTC to another party
🛠️ Install the SDK first. Every code example on this page uses the
cbtc-libRust SDK. If you have not installed it yet, start with SDK Setup and Installation — it covers installingcbtc-libandcanton-lib, uploading the CBTC DAR files to your participant node, and configuring your environment variables. Come back here once the verification check on that page passes.
Prerequisites for Minting CBTC
Section titled “Prerequisites for Minting CBTC”Before you begin minting wrapped Bitcoin on Canton, make sure you have the following:
| Requirement | Description |
|---|---|
| Canton participant node | A running Canton participant node connected to the network. See Canton documentation for setup. |
| DA Registry Utility | Installed and configured. See Digital Asset Utilities docs. |
| Keycloak credentials | A valid Keycloak host, realm, client ID, username, and password for your environment. |
| Party ID | Your Canton Party ID, obtained during onboarding. |
| Minter credential | Required to mint or burn. Issued to your party by the CBTC registrar as part of onboarding. Request one via sales@bitsafe.finance. See Minting and Burning for how to check and accept it. |
| Rust toolchain | If using cbtc-lib (Rust). Install via rustup.rs. |
| BTC to deposit | Real BTC (mainnet) or testnet BTC (testnet). For testnet, you can use the CBTC Testnet Faucet to get test CBTC directly. For mainnet, you mint CBTC by depositing real BTC. |
Choose Your CBTC Environment: Testnet or Mainnet
Section titled “Choose Your CBTC Environment: Testnet or Mainnet”CBTC is available on three environments. Start with testnet for experimentation, then move to mainnet for production. CBTC also exists on Devnet, but minting and withdrawals are not available to external users on Devnet. You can use the faucet to obtain test CBTC for development.
🧪 Testnet vs. Mainnet: what is identical and what differs
- Identical: DAR file, API surface, mint/burn flows, governance model, transfer mechanics
- Differs: Attestor set (smaller on testnet), confirmation times (may be faster), Instrument IDs (different from mainnet), faucet-only BTC on testnet (no real value)
- Mocked or unavailable on testnet: Real BTC settlement, production Attestor SLAs, mainnet fee structure
- Operational note: Testnet may be reset without notice. Testnet CBTC balances and transaction history may not persist across resets. Do not rely on testnet state for production planning.
Environment Configuration
Section titled “Environment Configuration”| Variable | Devnet | Testnet | Mainnet |
|---|---|---|---|
REGISTRY_URL |
https://api.utilities.digitalasset-dev.com |
https://api.utilities.digitalasset-staging.com |
https://api.utilities.digitalasset.com |
BITSAFE_API_URL |
https://api.devnet.bitsafe.finance |
https://api.testnet.bitsafe.finance |
https://api.mainnet.bitsafe.finance |
DECENTRALIZED_PARTY_ID |
cbtc-network::12202a83c6f4082217c175e29bc53da5f2703ba2675778ab99217a5a881a949203ff |
cbtc-network::12201b1741b63e2494e4214cf0bedc3d5a224da53b3bf4d76dba468f8e97eb15508f |
cbtc-network::12205af3b949a04776fc48cdcc05a060f6bda2e470632935f375d1049a8546a3b262 |
Set these as environment variables before running any commands:
export BITSAFE_API_URL="https://api.testnet.bitsafe.finance"export REGISTRY_URL="<your-registry-url>"export CANTON_NETWORK="canton-testnet"export DECENTRALIZED_PARTY_ID="<your-decentralized-party-id>"export KEYCLOAK_HOST="https://your-keycloak-host"export KEYCLOAK_REALM="your-realm"export KEYCLOAK_CLIENT_ID="your-client-id"export KEYCLOAK_USERNAME="your-username"export KEYCLOAK_PASSWORD="your-password"export LEDGER_HOST="https://your-ledger-host"export PARTY_ID="your-party-id"Step 1: Authenticate with Keycloak
Section titled “Step 1: Authenticate with Keycloak”All CBTC operations require a valid Keycloak access token. The canton-lib crate provides a helper for this.
Using cbtc-lib (Rust)
Section titled “Using cbtc-lib (Rust)”use keycloak::login::{password, token_url, PasswordParams};
let auth = password(PasswordParams { client_id: keycloak_client_id.clone(), username: keycloak_username.clone(), password: keycloak_password.clone(), url: token_url(&keycloak_host, &keycloak_realm),}).await?;
let access_token = auth.access_token;Using the Keycloak API directly
Section titled “Using the Keycloak API directly”curl -X POST "${KEYCLOAK_HOST}/realms/${KEYCLOAK_REALM}/protocol/openid-connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=password" \ -d "client_id=${KEYCLOAK_CLIENT_ID}" \ -d "username=${KEYCLOAK_USERNAME}" \ -d "password=${KEYCLOAK_PASSWORD}"Save the access_token from the response. You will pass it as a Bearer token in all subsequent API calls.
💡
api_urlin the code examples below is the BitSafe API base URL — the value you set asBITSAFE_API_URLabove. It serves the/cbtc/v1/*endpoints that back deposit accounts, deposit addresses, and withdrawals.
Step 2: Create a Deposit Account
Section titled “Step 2: Create a Deposit Account”A deposit account maps your Canton Party ID to a unique Bitcoin deposit address. You only need to create this once; the address can be reused for future deposits.
⚠️ This step requires a Minter credential. Deposit account creation fails without one. If you have not been issued a Minter credential yet, request one via sales@bitsafe.finance. See Minting and Burning for how to check for and accept your credential.
Using cbtc-lib
Section titled “Using cbtc-lib”use cbtc::mint_redeem::{mint, attestor};
// First get account rules from the Attestorlet account_rules = attestor::get_account_contract_rules(&api_url).await?;
let deposit_account = mint::create_deposit_account(mint::CreateDepositAccountParams { ledger_host: ledger_host.clone(), party: party_id.clone(), user_name: username.clone(), access_token: access_token.clone(), account_rules, credential_cids: minter_credential_cids.clone(),}).await?;Using the Canton API directly
Section titled “Using the Canton API directly”Submit a CreateDepositAccount command to the Canton Ledger API v2 endpoint. You can fetch the CBTCDepositAccountRules contract from an Attestor’s GET /cbtc/v1/account-contract-rules endpoint.
curl -X POST "${LEDGER_HOST}/v2/commands/submit-and-wait-for-transaction" \ --header 'Authorization: Bearer ${ACCESS_TOKEN}' \ --header 'Content-Type: application/json' \ --data '{ "commands": { "commands": [ { "ExerciseCommand": { "templateId": "#cbtc:CBTC.DepositAccount:CBTCDepositAccountRules", "contractId": "${DA_RULES_CID}", "choice": "CBTCDepositAccountRules_CreateDepositAccount", "choiceArgument": { "owner": "${OWNER_PARTY}", "credentialCids": ["${MINTER_CREDENTIAL_CID}"] } } } ], "actAs": [ "${OWNER_PARTY}" ], "commandId": "someCommandID", "disclosedContracts": [ { "templateId": "#cbtc:CBTC.DepositAccount:CBTCDepositAccountRules", "contractId": "${DA_RULES_CID}", "createdEventBlob": "${DA_RULES_BLOB}", "synchronizerId": "" } ] }}'ℹ️ Ephemeral contract IDs.
DA_RULES_CIDandDA_RULES_BLOBare ephemeral contract IDs. Query them from the Active Contract Service (POST /v2/state/active-contracts) at submit time. These IDs change after every consuming exercise.
Step 3: Get Your Bitcoin Deposit Address
Section titled “Step 3: Get Your Bitcoin Deposit Address”Once the deposit account is created, retrieve the Bitcoin address associated with it. This address is derived from the Deposit Account’s id field (or the contract_id if the Deposit Account is new).
Using cbtc-lib
Section titled “Using cbtc-lib”let btc_address = mint::get_bitcoin_address(mint::GetBitcoinAddressParams { api_url: api_url.clone(), account_id: deposit_account.account_id().to_string(),}).await?;
println!("Send BTC to: {}", btc_address);Using the BitSafe API directly
Section titled “Using the BitSafe API directly”Retrieve the deposit address from:
GET ${BITSAFE_API_URL}/cbtc/v1/bitcoin-address/{accountId}Use the deposit account’s id field as accountId.
Important: This address can be reused indefinitely for future deposits. You can also request additional deposit addresses if needed.
Step 4: Send BTC
Section titled “Step 4: Send BTC”Send Bitcoin to the deposit address using any standard Bitcoin wallet or tooling.
⚠️ Deposits are subject to per-account limits. By default the minimum is 0.0001 BTC and the maximum is 5 BTC. A deposit outside that range will not be minted. Limits are set per account and can be adjusted on request - see Transaction Limits for how to read your account’s actual limits at runtime rather than assuming the defaults.
Bitcoin address: [your deposit address from Step 3]After sending, you need to wait for 6 Bitcoin block confirmations before the Attestor network will process the deposit.
Step 5: Wait for Confirmations and Auto-Minting
Section titled “Step 5: Wait for Confirmations and Auto-Minting”Once your BTC transaction reaches 6 confirmations:
- The Attestor network detects the confirmed deposit
- Each Attestor independently verifies the transaction
- When a threshold of Attestors confirm (via
ConfirmDepositActionon the CBTC Governance module), CBTC is automatically minted to your Canton Party - No further action is required from you This process typically takes 60 to 90 minutes, depending on Bitcoin block times.
Step 6: Check Your CBTC Balance
Section titled “Step 6: Check Your CBTC Balance”Using cbtc-lib
Section titled “Using cbtc-lib”use cbtc::active_contracts;
let holdings = active_contracts::get(active_contracts::Params { ledger_host: ledger_host.clone(), party: party_id.clone(), access_token: access_token.clone(), instrument_id: cbtc::InstrumentId { admin: decentralized_party_id.clone(), id: cbtc::CBTC_TICKER.to_string(), }, // `None` reads every holding the party owns. account: None,}).await?;
println!("CBTC holdings: {} contract(s)", holdings.len());Using the Canton API directly
Section titled “Using the Canton API directly”Query active contracts filtered by the token holding interface:
curl -X POST "${LEDGER_HOST}/v2/state/active-contracts" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "eventFormat": { "filtersByParty": { "'"${PARTY_ID}"'": { "cumulative": [{ "identifierFilter": { "InterfaceFilter": { "value": { "interfaceId": "#splice-api-token-holding-v1:Splice.Api.Token.HoldingV1:Holding", "includeInterfaceView": true, "includeCreatedEventBlob": true } } } }] } }, "verbose": false }, "activeAtOffset": '"${LATEST_OFFSET}"' }'💡 Filtering required. This query returns all token holdings, including Canton Coin (CC). To isolate your CBTC balance, you must filter the results client-side by the CBTC
instrumentId. Thecbtc-liblibrary handles this automatically viaactive_contracts::get. There is no single curl that can query and filter for CBTC holdings in one step. TheactiveAtOffsetmust be set to the actual latest offset from your ledger.
💡 UTXO model. CBTC uses a UTXO model similar to Bitcoin. Your balance may be spread across multiple holding contracts (soft limit of 10 UTXOs per party per token type). Use the
cbtc::consolidatemodule to merge UTXOs, orcbtc::splitto divide them.
Step 7: Transfer CBTC Between Canton Parties
Section titled “Step 7: Transfer CBTC Between Canton Parties”A CBTC transfer has one of two outcomes. The registry creates a transfer offer, and the receiver accepts it. Or the registry settles the transfer on submission, and the receiver has nothing to accept. The registry settles on submission for a self transfer and for a receiver that pre-approved the sender. transfer::submit returns a TransferReceipt, and its outcome field tells you which outcome occurred.
Phase 1: Submit the transfer (sender)
Section titled “Phase 1: Submit the transfer (sender)”use cbtc::transfer;
let receipt = transfer::submit(transfer::Params { transfer: cbtc::Transfer { sender: sender_party_id.clone(), receiver: receiver_party_id.clone(), amount: cbtc::DamlDecimal::parse("0.01")?, instrument_id: cbtc::InstrumentId { admin: decentralized_party_id.clone(), id: "CBTC".to_string(), }, requested_at: chrono::Utc::now().to_rfc3339(), execute_before: (chrono::Utc::now() + chrono::Duration::hours(168)).to_rfc3339(), input_holding_cids: None, meta: None, }, ledger_host: ledger_host.clone(), access_token: access_token.clone(), registry_url: registry_url.clone(), decentralized_party_id: decentralized_party_id.clone(),}).await?;Phase 2: Accept the transfer (receiver)
Section titled “Phase 2: Accept the transfer (receiver)”Do this step only when receipt.outcome is TransferOutcome::Pending. receipt.outcome.transfer_offer_cid() returns the offer ID. A Completed outcome leaves no offer to accept.
use cbtc::accept;
accept::submit(accept::Params { transfer_offer_contract_id: transfer_contract_id.clone(), receiver_party: receiver_party_id.clone(), ledger_host: ledger_host.clone(), access_token: receiver_access_token.clone(), registry_url: registry_url.clone(), decentralized_party_id: decentralized_party_id.clone(),}).await?;ℹ️ No curl example for transfers. The raw API transfer flow requires 5-6 sequential API calls with contract disclosures and is too complex to represent as a single curl example. Use
cbtc-libfor transfers, or refer to the Canton Utility docs for the full API sequence.
💡 This is a free-of-payment (FOP) transfer. CBTC moves in one direction with nothing exchanged in return. If you need delivery-versus-payment — where CBTC only moves if a second leg moves atomically with it — use the
cbtc::allocationmodule instead. See DvP Settlement Using Allocations in the Integration Guides.
Redeem CBTC: Convert Wrapped Bitcoin Back to BTC
Section titled “Redeem CBTC: Convert Wrapped Bitcoin Back to BTC”To convert CBTC back to BTC (this also requires a Minter credential):
- Create a withdraw account specifying your Bitcoin destination address using the
cbtc::mint_redeem::redeemmodule - Submit the withdrawal which burns CBTC on Canton
- The Attestor network detects the burn and constructs a Bitcoin transaction
- Attestors sign the transaction via threshold signing (FROST)
- The BTC transaction is broadcast to the Bitcoin network
Using cbtc-lib
Section titled “Using cbtc-lib”use cbtc::mint_redeem::redeem;
// 1. Create a withdraw account with your BTC destination addresslet withdraw_account = redeem::create_withdraw_account(redeem::CreateWithdrawAccountParams { ledger_host: ledger_host.clone(), party: party_id.clone(), user_name: username.clone(), access_token: access_token.clone(), account_rules_contract_id: rules.wa_rules.contract_id.clone(), account_rules_template_id: rules.wa_rules.template_id.clone(), account_rules_created_event_blob: rules.wa_rules.created_event_blob.clone(), destination_btc_address: btc_destination_address.clone(), credential_cids: minter_credential_cids.clone(),}).await?;
// 2. Submit the withdrawal (burns CBTC, Attestors process BTC payout)let updated_account = redeem::submit_withdraw(redeem::SubmitWithdrawParams { ledger_host: ledger_host.clone(), party: party_id.clone(), user_name: username.clone(), access_token: access_token.clone(), api_url: api_url.clone(), withdraw_account_contract_id: withdraw_account.contract_id.clone(), amount: cbtc::DamlDecimal::parse("0.001")?, holding_contract_ids: holding_ids, credential_cids: Some(minter_credential_cids.clone()),}).await?;
println!("Pending balance: {}", updated_account.pending_balance);Using the Canton API directly
Section titled “Using the Canton API directly”Submit a CBTCWithdrawAccount_Withdraw command. You can get the correct extraArgs and contract disclosures from an Attestor’s GET /cbtc/v1/token-standard-contracts endpoint. The contractIds, templateIds, and blobs in this example are for illustration only:
curl -X POST "${LEDGER_HOST}/v2/commands/submit-and-wait-for-transaction" \ --header 'Authorization: Bearer ${ACCESS_TOKEN}' \ --header 'Content-Type: application/json' \ --data '{ "commands": { "commands": [ { "ExerciseCommand": { "templateId": "#cbtc:CBTC.WithdrawAccount:CBTCWithdrawAccount", "contractId": "${WA_CID}", "choice": "CBTCWithdrawAccount_Withdraw", "choiceArgument": { "amount": "${AMOUNT}", "tokens": [ "${INPUT_HOLDING}" ], "burnMintFactoryCid": "${ALLOCATION_FACTORY_CID}", "extraArgs": { "context": { "values": { "utility.digitalasset.com/instrument-configuration": { "tag": "AV_ContractId", "value": "00da61fcfa2d9b358c606f040a1d635fbabe4265d33908744a148a80be0dcdc383ca111220e86787467ef7be665f68b813a30de6b0480955dcb514ef29832720618375dee5" }, "utility.digitalasset.com/app-reward-configuration": { "tag": "AV_ContractId", "value": "00bee23612f3c82dd6091e64ffde81cb535e7b446d7ebdf6d91329502c6023d1cdca1112209569dade5e20bf97f963882f6387293034a82f716d2b0df28820a82048f5f86c" }, "utility.digitalasset.com/featured-app-right": { "tag": "AV_ContractId", "value": "001c6e92181f8de9e4d15956e33f90db3085c518747de627134b6850459bf3662fca111220ee87bf1e3b05ed4d3f8a6c336290660f23409cb8d11a028e1073137eac8f78bf" }, "utility.digitalasset.com/issuer-credentials": { "tag": "AV_List", "value": [ { "tag": "AV_ContractId", "value": "0058feffeeaa48cc5526c723c4877af4f6abe3c76131c54ba0d63c6da299f8681bca111220b7032514b6b448d75ea6d9eefe01f0a345504e897a5f2e72fabf974322b20b07" } ] } } }, "meta": { "values": { "splice.lfdecentralizedtrust.org/reason": "CBTC Burn" } } } } } } ], "actAs": [ "${OWNER_PARTY}" ], "commandId": "someCommandId", "disclosedContracts": [ { "templateId": "82798df018301852704f210b97adaabf76d3ecd37d889e1bf96b5f31a20eea34:Utility.Registry.App.V0.Service.AllocationFactory:AllocationFactory", "contractId": "00d58a5f061f086b3c4b405b57ca08f4cefcb7f3a27a26089be6388eb62ee619a8ca1112200991d565daa04ca691901bc04238a8655e2c068039130e4c00027eac2675d9f4", "createdEventBlob": "CgMyLjEShwYKRQDVil8GHwhrPEtAW1fKCPTO/LfzonomCJvmOI62LuYZqMoREiAJkdVl2qBMppGQG8BCOKhlXiwGgDkTDkwAAn6sJnXZ9BIXdXRpbGl0eS1yZWdpc3RyeS1hcHAtdjAajQEKQDgyNzk4ZGYwMTgzMDE4NTI3MDRmMjEwYjk3YWRhYWJmNzZkM2VjZDM3ZDg4OWUxYmY5NmI1ZjMxYTIwZWVhMzQSB1V0aWxpdHkSCFJlZ2lzdHJ5EgNBcHASAlYwEgdTZXJ2aWNlEhFBbGxvY2F0aW9uRmFjdG9yeRoRQWxsb2NhdGlvbkZhY3RvcnkioQJqngIKVgpUOlJjYnRjLW5ldHdvcms6OjEyMjAyYTgzYzZmNDA4MjIxN2MxNzVlMjliYzUzZGE1ZjI3MDNiYTI2NzU3NzhhYjk5MjE3YTVhODgxYTk0OTIwM2ZmClYKVDpSY2J0Yy1uZXR3b3JrOjoxMjIwMmE4M2M2ZjQwODIyMTdjMTc1ZTI5YmM1M2RhNWYyNzAzYmEyNjc1Nzc4YWI5OTIxN2E1YTg4MWE5NDkyMDNmZgpsCmo6aGF1dGgwXzAwN2M2NWY4NTdmMWMzZDU5OWNiNmRmNzM3NzU6OjEyMjBkMmQ3MzJkMDQyYzI4MWNlZTgwZjQ4M2FiODBmM2NiYWE0NzgyODYwZWQ1ZjRkYzIyOGFiMDNkZWRkMmVlOGY5KlJjYnRjLW5ldHdvcms6OjEyMjAyYTgzYzZmNDA4MjIxN2MxNzVlMjliYzUzZGE1ZjI3MDNiYTI2NzU3NzhhYjk5MjE3YTVhODgxYTk0OTIwM2ZmMmhhdXRoMF8wMDdjNjVmODU3ZjFjM2Q1OTljYjZkZjczNzc1OjoxMjIwZDJkNzMyZDA0MmMyODFjZWU4MGY0ODNhYjgwZjNjYmFhNDc4Mjg2MGVkNWY0ZGMyMjhhYjAzZGVkZDJlZThmOTlubOVh5DkGAEIqCiYKJAgBEiCdDhxHJbSFz7Snbvg8xLkPDPvaP3wl+HzTfq2LxHAGmRAe", "synchronizerId": "" }, { "templateId": "3ca1343ab26b453d38c8adb70dca5f1ead8440c42b59b68f070786955cbf9ec1:Splice.Amulet:FeaturedAppRight", "contractId": "001c6e92181f8de9e4d15956e33f90db3085c518747de627134b6850459bf3662fca111220ee87bf1e3b05ed4d3f8a6c336290660f23409cb8d11a028e1073137eac8f78bf", "createdEventBlob": "CgMyLjESvQQKRQAcbpIYH43p5NFZVuM/kNswhcUYdH3mJxNLaFBFm/NmL8oREiDuh78eOwXtTT+KbDNikGYPI0CcuNEaAo4QcxN+rI94vxINc3BsaWNlLWFtdWxldBpkCkAzY2ExMzQzYWIyNmI0NTNkMzhjOGFkYjcwZGNhNWYxZWFkODQ0MGM0MmI1OWI2OGYwNzA3ODY5NTVjYmY5ZWMxEgZTcGxpY2USBkFtdWxldBoQRmVhdHVyZWRBcHBSaWdodCKqAWqnAQpNCks6SURTTzo6MTIyMGJlNThjMjllNjVkZTQwYmYyNzNiZTFkYzJiMjY2ZDQzYTlhMDAyZWE1YjE4OTU1YWVlZjdhYWM4ODFiYjQ3MWEKVgpUOlJjYnRjLW5ldHdvcms6OjEyMjAyYTgzYzZmNDA4MjIxN2MxNzVlMjliYzUzZGE1ZjI3MDNiYTI2NzU3NzhhYjk5MjE3YTVhODgxYTk0OTIwM2ZmKklEU086OjEyMjBiZTU4YzI5ZTY1ZGU0MGJmMjczYmUxZGMyYjI2NmQ0M2E5YTAwMmVhNWIxODk1NWFlZWY3YWFjODgxYmI0NzFhMlJjYnRjLW5ldHdvcms6OjEyMjAyYTgzYzZmNDA4MjIxN2MxNzVlMjliYzUzZGE1ZjI3MDNiYTI2NzU3NzhhYjk5MjE3YTVhODgxYTk0OTIwM2ZmOVDK9pNvQQYAQioKJgokCAESIOo7ddJGewmnI6M13zqEh5d8VRVxWJ73gao2y0uty955EB4=", "synchronizerId": "" }, { "templateId": "ed73d5b9ab717333f3dbd122de7be3156f8bf2614a67360c3dd61fc0135133fa:Utility.Registry.V0.Configuration.Instrument:InstrumentConfiguration", "contractId": "00da61fcfa2d9b358c606f040a1d635fbabe4265d33908744a148a80be0dcdc383ca111220e86787467ef7be665f68b813a30de6b0480955dcb514ef29832720618375dee5", "createdEventBlob": "CgMyLjESnwkKRQDaYfz6LZs1jGBvBAodY1+6vkJl0zkIdEoUioC+Dc3Dg8oREiDoZ4dGfve+Zl9ouBOjDeawSAlV3LUU7ymDJyBhg3Xe5RITdXRpbGl0eS1yZWdpc3RyeS12MBqNAQpAZWQ3M2Q1YjlhYjcxNzMzM2YzZGJkMTIyZGU3YmUzMTU2ZjhiZjI2MTRhNjczNjBjM2RkNjFmYzAxMzUxMzNmYRIHVXRpbGl0eRIIUmVnaXN0cnkSAlYwEg1Db25maWd1cmF0aW9uEgpJbnN0cnVtZW50GhdJbnN0cnVtZW50Q29uZmlndXJhdGlvbiK9BWq6BQpsCmo6aGF1dGgwXzAwN2M2NWY4NTdmMWMzZDU5OWNiNmRmNzM3NzU6OjEyMjBkMmQ3MzJkMDQyYzI4MWNlZTgwZjQ4M2FiODBmM2NiYWE0NzgyODYwZWQ1ZjRkYzIyOGFiMDNkZWRkMmVlOGY5ClYKVDpSY2J0Yy1uZXR3b3JrOjoxMjIwMmE4M2M2ZjQwODIyMTdjMTc1ZTI5YmM1M2RhNWYyNzAzYmEyNjc1Nzc4YWI5OTIxN2E1YTg4MWE5NDkyMDNmZgpWClQ6UmNidGMtbmV0d29yazo6MTIyMDJhODNjNmY0MDgyMjE3YzE3NWUyOWJjNTNkYTVmMjcwM2JhMjY3NTc3OGFiOTkyMTdhNWE4ODFhOTQ5MjAzZmYKhAEKgQFqfwpWClQ6UmNidGMtbmV0d29yazo6MTIyMDJhODNjNmY0MDgyMjE3YzE3NWUyOWJjNTNkYTVmMjcwM2JhMjY3NTc3OGFiOTkyMTdhNWE4ODFhOTQ5MjAzZmYKCAoGQgRDQlRDChsKGUIXUmVnaXN0cmFySW50ZXJuYWxTY2hlbWUKigEKhwFahAEKgQFqfwpWClQ6UmNidGMtbmV0d29yazo6MTIyMDJhODNjNmY0MDgyMjE3YzE3NWUyOWJjNTNkYTVmMjcwM2JhMjY3NTc3OGFiOTkyMTdhNWE4ODFhOTQ5MjAzZmYKCAoGQgRDQlRDChsKGUIXUmVnaXN0cmFySW50ZXJuYWxTY2hlbWUKBAoCWgAKBAoCWgAKegp4UnYKdFpyCnBqbgpaClg6VmNidGMtYmVuZWZpY2lhcnk6OjEyMjBmYTg1NDNkYjZjNjZmZTNhNTViMWYxODBjOGRmYzdmODc2MjY1Yzc2Njg0ZmJjMWQzNWQ4OWUwMmM4YWFmZThlChAKDjIMMS4wMDAwMDAwMDAwKlJjYnRjLW5ldHdvcms6OjEyMjAyYTgzYzZmNDA4MjIxN2MxNzVlMjliYzUzZGE1ZjI3MDNiYTI2NzU3NzhhYjk5MjE3YTVhODgxYTk0OTIwM2ZmMmhhdXRoMF8wMDdjNjVmODU3ZjFjM2Q1OTljYjZkZjczNzc1OjoxMjIwZDJkNzMyZDA0MmMyODFjZWU4MGY0ODNhYjgwZjNjYmFhNDc4Mjg2MGVkNWY0ZGMyMjhhYjAzZGVkZDJlZThmOTnz2t5E5kEGAEIqCiYKJAgBEiAb19AdEjIoV/7xtMY8A2/BkXnnQHGPcCNC0UEu/b5/YxAe", "synchronizerId": "" }, { "templateId": "77df4e7b980c12de438d7b052141a762215fae790d81f71179c8fb534beb68f7:Utility.Credential.V0.Credential:Credential", "contractId": "0058feffeeaa48cc5526c723c4877af4f6abe3c76131c54ba0d63c6da299f8681bca111220b7032514b6b448d75ea6d9eefe01f0a345504e897a5f2e72fabf974322b20b07", "createdEventBlob": "CgMyLjESjQgKRQBY/v/uqkjMVSbHI8SHevT2q+PHYTHFS6DWPG2imfhoG8oREiC3AyUUtrRI116m2e7+AfCjRVBOiXpfLnL6v5dDIrILBxIVdXRpbGl0eS1jcmVkZW50aWFsLXYwGnMKQDc3ZGY0ZTdiOTgwYzEyZGU0MzhkN2IwNTIxNDFhNzYyMjE1ZmFlNzkwZDgxZjcxMTc5YzhmYjUzNGJlYjY4ZjcSB1V0aWxpdHkSCkNyZWRlbnRpYWwSAlYwEgpDcmVkZW50aWFsGgpDcmVkZW50aWFsIuwDaukDCloKWDpWaUJUQy12YWxpZGF0b3ItMTo6MTIyMGZhODU0M2RiNmM2NmZlM2E1NWIxZjE4MGM4ZGZjN2Y4NzYyNjVjNzY2ODRmYmMxZDM1ZDg5ZTAyYzhhYWZlOGUKVgpUOlJjYnRjLW5ldHdvcms6OjEyMjAyYTgzYzZmNDA4MjIxN2MxNzVlMjliYzUzZGE1ZjI3MDNiYTI2NzU3NzhhYjk5MjE3YTVhODgxYTk0OTIwM2ZmChIKEEIOY2J0Yy1wcm92LWhhY2sKDgoMQgpXb3JrYXJvdW5kCgQKAlIACgQKAlIACoQBCoEBWn8KfWp7ClYKVEJSY2J0Yy1uZXR3b3JrOjoxMjIwMmE4M2M2ZjQwODIyMTdjMTc1ZTI5YmM1M2RhNWYyNzAzYmEyNjc1Nzc4YWI5OTIxN2E1YTg4MWE5NDkyMDNmZgoTChFCD2hhc1JlZ2lzdHJ5Um9sZQoMCgpCCFByb3ZpZGVyCnwKemp4CnYKdGJyCnAKajpoYXV0aDBfMDA3YzY1Zjg1N2YxYzNkNTk5Y2I2ZGY3Mzc3NTo6MTIyMGQyZDczMmQwNDJjMjgxY2VlODBmNDgzYWI4MGYzY2JhYTQ3ODI4NjBlZDVmNGRjMjI4YWIwM2RlZGQyZWU4ZjkSAgoAKlJjYnRjLW5ldHdvcms6OjEyMjAyYTgzYzZmNDA4MjIxN2MxNzVlMjliYzUzZGE1ZjI3MDNiYTI2NzU3NzhhYjk5MjE3YTVhODgxYTk0OTIwM2ZmKlZpQlRDLXZhbGlkYXRvci0xOjoxMjIwZmE4NTQzZGI2YzY2ZmUzYTU1YjFmMTgwYzhkZmM3Zjg3NjI2NWM3NjY4NGZiYzFkMzVkODllMDJjOGFhZmU4ZTJoYXV0aDBfMDA3YzY1Zjg1N2YxYzNkNTk5Y2I2ZGY3Mzc3NTo6MTIyMGQyZDczMmQwNDJjMjgxY2VlODBmNDgzYWI4MGYzY2JhYTQ3ODI4NjBlZDVmNGRjMjI4YWIwM2RlZGQyZWU4Zjk5iBIJ/7RABgBCKgomCiQIARIgwrTBnrlaL0STx5Hh0v7BEmbbCERCZDgwJWqqcljhQZ0QHg==", "synchronizerId": "" } ] }}'Additional Operations
Section titled “Additional Operations”The cbtc-lib library provides several utility modules for managing your CBTC holdings:
| Module | Purpose |
|---|---|
cbtc::batch |
Batch operations for sending CBTC from a CSV file |
cbtc::distribute |
Distribute CBTC across multiple parties |
cbtc::consolidate |
Merge multiple UTXO holdings into fewer contracts |
cbtc::split |
Split a single holding into multiple UTXOs |
cbtc::active_contracts |
Query your current CBTC holdings |
Troubleshooting
Section titled “Troubleshooting”My deposit has not been minted after 90 minutes
- Verify the BTC transaction has at least 6 confirmations on a block explorer
- Confirm you sent to the correct deposit address (from Step 3)
- Check that your Canton participant node is connected and syncing
- Escalation: contact support@bitsafe.finance
Transfer offer is not appearing for the receiver
- The receiver must be registered in the DA Registry with a valid credential
- Confirm the receiver’s Party ID is correct
- Check that both parties are connected to the same Canton sync domain
"Insufficient holdings" error when sending
- CBTC uses a UTXO model. You may need to consolidate holdings first using
cbtc::consolidate - Check your balance with
cbtc::active_contractsto verify available amounts
Authentication token expired
- Keycloak tokens have a limited lifetime. Re-authenticate using Step 1 before retrying the operation
Next Steps
Section titled “Next Steps”- CBTC Minting and Burning - The same mint and burn flows in more depth, with error handling and recovery patterns for production
- API Reference - Canton Ledger API endpoints, instrument IDs, rate limits, and error codes
- SDK Setup and Installation - Full
cbtc-libandcanton-libmodule reference - Authentication Guide - Detailed Keycloak setup and an Auth0 community example
- Integration Guides - Patterns for DeFi protocols, wallets, custody, and trading systems
- Withdraw via curl - The raw API withdrawal sequence, without the SDK
📧 Need help? Reach out to support@bitsafe.finance
Ask the docs
Answers come only from this documentation and can be wrong. Check the linked pages.
Ask a question about BitSafe, CBTC or the developer guides. For example: