Skip to main content

Using ckBTC in dapps

Advanced
Bitcoin
Chain-key tokens

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 the 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 ckbtcLedger "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<ckbtcLedger.BlockIndex, Text> {
    Debug.print(
      "Transferring "
      # debug_show (args.amount)
      # " tokens to account"
      # debug_show (args.toAccount)
    );

    let transferArgs : ckbtcLedger.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 ckbtcLedger.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