# 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 (two-phase transfer)

***

## 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](https://docs.digitalasset.com/canton) for setup.                                                                             |
| **DA Registry Utility**     | Installed and configured. See [Digital Asset Utilities docs](https://docs.digitalasset.com/utilities/mainnet/index.html).                                                                                           |
| **Keycloak credentials**    | A valid Keycloak host, realm, client ID, username, and password for your environment.                                                                                                                               |
| **Party ID**                | Your Canton Party ID, obtained during onboarding.                                                                                                                                                                   |
| **Rust toolchain**          | If using cbtc-lib (Rust). Install via [rustup.rs](https://rustup.rs/).                                                                                                                                              |
| **BTC to deposit**          | Real BTC (mainnet) or testnet BTC (testnet). For testnet, you can use the [CBTC Testnet Faucet](https://cbtc-faucet.bitsafe.finance/) to get test CBTC directly. For mainnet, you mint CBTC by depositing real BTC. |

***

## 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, two-phase 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. *Exact reset schedule and data persistence details to be confirmed with Engineering.*

### Environment Configuration

| Variable                 | Devnet                                                                                      | Testnet                                                                                             | Mainnet                                                                             |
| ------------------------ | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `REGISTRY_URL`           | [`https://api.utilities.digitalasset-dev.com`](https://api.utilities.digitalasset-dev.com/) | [`https://api.utilities.digitalasset-staging.com`](https://api.utilities.digitalasset-staging.com/) | [`https://api.utilities.digitalasset.com`](https://api.utilities.digitalasset.com/) |
| `ATTESTOR_URL`           | [`https://devnet.dlc.link/attestor-2`](https://devnet.dlc.link/attestor-2)                  | [`https://testnet.dlc.link/attestor-1`](https://testnet.dlc.link/attestor-1)                        | [`https://mainnet.dlc.link/attestor-1`](https://mainnet.dlc.link/attestor-1)        |
| `DECENTRALIZED_PARTY_ID` | *Environment-specific. Provided during onboarding.*                                         | *Environment-specific. Provided during onboarding.*                                                 | *Environment-specific. Provided during onboarding.*                                 |

Set these as environment variables before running any commands:

```bash
export ATTESTOR_URL="https://testnet.dlc.link/attestor-1"
export CANTON_NETWORK="canton-testnet"
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

All CBTC operations require a valid Keycloak access token. The `canton-lib` crate provides a helper for this.

### Using cbtc-lib (Rust)

```rust
use keycloak::login::{password, password_url, PasswordParams};

let auth = password(PasswordParams {
 client_id: keycloak_client_id.clone(),
 username: keycloak_username.clone(),
 password: keycloak_password.clone(),
 url: password_url(&keycloak_host, &keycloak_realm),
}).await?;

let access_token = auth.access_token;
```

### Using the Keycloak API directly

```bash
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.

***

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

### Using cbtc-lib

```rust
use cbtc::mint_redeem::{mint, attestor};

// First get account rules from the Attestor
let account_rules = attestor::get_account_contract_rules(
 &attestor_url,
 &canton_network,
).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,
}).await?;
```

### 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 `/app/get-account-contract-rules` endpoint.

```bash
curl -X POST '${LEDGER_HOST}/v2/commands/submit-and-wait-for-transaction-tree' \
 --header 'Authorization: Bearer ${ACCESS_TOKEN}' \
 --data '{
 "commands": [
 {
 "ExerciseCommand": {
 "templateId": "#cbtc:CBTC.DepositAccount:CBTCDepositAccountRules",
 "contractId": "${DA_RULES_CID}",
 "choice": "CBTCDepositAccountRules_CreateDepositAccount",
 "choiceArgument": {
 "owner": "${OWNER_PARTY}"
 }
 }
 }
 ],
 "actAs": [
 "${OWNER_PARTY}"
 ],
 "commandId": "someCommandID",
 "disclosedContracts": [
 {
 "templateId": "#cbtc:CBTC.DepositAccount:CBTCDepositAccountRules",
 "contractId": "${DA_RULES_CID}",
 "createdEventBlob": "${DA_RULES_BLOB}",
 "synchronizerId": ""
 }
 ]
}'
```

***

## 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

```rust
let btc_address = mint::get_bitcoin_address(mint::GetBitcoinAddressParams {
 attestor_url: attestor_url.clone(),
 account_id: deposit_account.contract_id.clone(),
 chain: canton_network.clone(),
}).await?;

println!("Send BTC to: {}", btc_address);
```

**Important:** This address can be reused indefinitely for future deposits. You can also request additional deposit addresses if needed.

***

## Step 4: Send BTC

Send Bitcoin to the deposit address using any standard Bitcoin wallet or tooling. There is no minimum deposit enforced at the protocol level, but check with BitSafe for any operational minimums.

```javascript
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

Once your BTC transaction reaches 6 confirmations:

1. The **Attestor network** detects the confirmed deposit
2. Each Attestor independently verifies the transaction
3. When a threshold of Attestors confirm (via `ConfirmDepositAction` on the CBTC Governance module), CBTC is **automatically minted** to your Canton Party
4. 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

### Using cbtc-lib

```rust
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(),
}).await?;

println!("CBTC holdings: {} contract(s)", holdings.len());
```

### Using the Canton API directly

Query active contracts filtered by the token holding interface:

```bash
curl -X POST "${LEDGER_HOST}/v2/state/active-contracts" \
 -H "Authorization: Bearer ${ACCESS_TOKEN}" \
 -H "Content-Type: application/json" \
 -d '{
 "filter": {
 "interfaceFilters": [{
 "interfaceId": "#splice-api-token-holding-v1:Splice.Api.Token.HoldingV1:Holding"
 }]
 },
 "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`. The `cbtc-lib` library handles this automatically via `active_contracts::get`. There is no single curl that can query and filter for CBTC holdings in one step. The `activeAtOffset` must 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::consolidate` module to merge UTXOs, or `cbtc::split` to divide them.

***

## Step 7: Transfer CBTC Between Canton Parties

CBTC transfers use a **two-phase model**: the sender creates an offer, and the receiver accepts it. This ensures both parties explicitly consent to the transfer.

### Phase 1: Create a transfer offer (sender)

```rust
use cbtc::transfer;

transfer::submit(transfer::Params {
 transfer: common::transfer::Transfer {
 sender: sender_party_id.clone(),
 receiver: receiver_party_id.clone(),
 amount: "0.01".to_string(),
 instrument_id: common::transfer::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)

```rust
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-lib` for transfers, or refer to the [Canton Utility docs](https://docs.digitalasset.com/utilities/mainnet/index.html) for the full API sequence.

***

## Redeem CBTC: Convert Wrapped Bitcoin Back to BTC

To convert CBTC back to BTC:

1. **Create a withdraw account** specifying your Bitcoin destination address using the `cbtc::mint_redeem::redeem` module
2. **Submit the withdrawal** which burns CBTC on Canton
3. The **Attestor network** detects the burn and constructs a Bitcoin transaction
4. Attestors sign the transaction via threshold signing (FROST)
5. The BTC transaction is broadcast to the Bitcoin network

### Using cbtc-lib

```rust
use cbtc::mint_redeem::redeem;

// 1. Create a withdraw account with your BTC destination address
let 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(),
}).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(),
 attestor_url: attestor_url.clone(),
 chain: canton_network.clone(),
 withdraw_account_contract_id: withdraw_account.contract_id.clone(),
 withdraw_account_template_id: withdraw_account.template_id.clone(),
 withdraw_account_created_event_blob: withdraw_account.created_event_blob.clone(),
 amount: "0.001".to_string(),
 holding_contract_ids: holding_ids,
}).await?;

println!("Pending balance: {}", updated_account.pending_balance);
```

### Using the Canton API directly

Submit a `CBTCWithdrawAccount_Withdraw` command. You can get the correct `extraArgs` and contract disclosures from an Attestor's `/app/get-token-standard-contracts` endpoint. The `contractIds`, `templateIds`, and `blobs` in this example are for illustration only:

```bash
curl -X POST '${LEDGER_HOST}/v2/commands/submit-and-wait-for-transaction-tree' \
 --header 'Authorization: Bearer ${ACCESS_TOKEN}' \
 --data '{
 "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

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

<details>

<summary>My deposit has not been minted after 90 minutes</summary>

* 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>

</details>

<details>

<summary>Transfer offer is not appearing for the receiver</summary>

* 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

</details>

<details>

<summary>"Insufficient holdings" error when sending</summary>

* CBTC uses a UTXO model. You may need to consolidate holdings first using `cbtc::consolidate`
* Check your balance with `cbtc::active_contracts` to verify available amounts

</details>

<details>

<summary>Authentication token expired</summary>

* Keycloak tokens have a limited lifetime. Re-authenticate using Step 1 before retrying the operation

</details>

***

## Next Steps

* **API Reference:** Full documentation of Canton Ledger API endpoints for CBTC operations *(coming soon)*
* **SDK Reference:** Complete `cbtc-lib` and `canton-lib` module documentation *(coming soon)*
* **Authentication Guide:** Detailed Keycloak setup and Auth0 community example *(coming soon)*
* **Integration Examples:** Real-world code showing CBTC in DeFi protocols, wallets, and trading systems *(coming soon)*

***

> 📧 **Need help?** Reach out to <support@bitsafe.finance>

> ⚠️ **Code updated per PR #22.** All Rust code examples have been updated to match the actual `cbtc` v0.3.0 and `canton-lib` v0.3.0 APIs (correct module paths, struct params, function signatures). Curl examples updated to use `${LEDGER_HOST}` without port. Final engineering sign-off by Jesse or Ferenc is still required before external publication.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.bitsafe.finance/developers/cbtc-quick-start.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
