Skip to main content

Using ckBTC in dapps

Advanced
Bitcoin

Overview

ckBTC is an ICRC-2 compliant token, meaning it supports the ICRC-1 and ICRC-2 token standards.

To integrate ckBTC into your dapp, you can write code that uses the ICRC-1 standard. For token transfers and account balances, ckBTC requests must be sent to ICRC-1 ledger canister with principal ID mxzaz-hqaaa-aaaar-qaada-cai.

Write a smart contract that uses ckBTC

To write canister code that makes calls to the ICRC-1 ledger, you will need to use inter-canister calls and specify the principal ID of the ICRC-1 ledger canister. Then, to interact with ckBTC, you can use the ICRC-1 endpoints.

You can deploy the ICRC-1 ledger locally for testing. Learn more in the ICRC-1 ledger setup documentation.

Once you have your project configured to use the ICRC-1 ledger, you can interact with it for workflows such as transferring tokens:

import Icrc1Ledger "canister:icrc1_ledger_canister";
import Debug "mo:base/Debug";
import Result "mo:base/Result";
import Option "mo:base/Option";
import Blob "mo:base/Blob";
import Error "mo:base/Error";

actor {

type Account = {
owner : Principal;
subaccount : ?[Nat8];
};

type TransferArgs = {
amount : Nat;
toAccount : Account;
};

public shared ({ caller }) func transfer(args : TransferArgs) : async Result.Result<Icrc1Ledger.BlockIndex, Text> {
Debug.print(
"Transferring "
# debug_show (args.amount)
# " tokens to account"
# debug_show (args.toAccount)
);

let transferArgs : Icrc1Ledger.TransferArg = {
// can be used to distinguish between transactions
memo = null;
// the amount we want to transfer
amount = args.amount;
// we want to transfer tokens from the default subaccount of the canister
from_subaccount = null;
// if not specified, the default fee for the canister is used
fee = null;
// we take the principal and subaccount from the arguments and convert them into an account identifier
to = args.toAccount;
// a timestamp indicating when the transaction was created by the caller; if it is not specified by the caller then this is set to the current ICP time
created_at_time = null;
};

try {
// initiate the transfer
let transferResult = await Icrc1Ledger.icrc1_transfer(transferArgs);

// check if the transfer was successful
switch (transferResult) {
case (#Err(transferError)) {
return #err("Couldn't transfer funds:\n" # debug_show (transferError));
};
case (#Ok(blockIndex)) { return #ok blockIndex };
};
} catch (error : Error) {
// catch any errors that might occur during the transfer
return #err("Reject message: " # Error.message(error));
};
};
};

View the ICRC-1 endpoints for more information on sending and receiving tokens.

ckBTC production application examples