Skip to main content

View and Transfer a CKB Balance

⏰ Estimated Time: 2 - 5 min
🔧 What You Will Need:
For detailed installation steps, refer to our Installation Guide

Tutorial Overview

CKB is based on a UTXO-like Cell Model

. Every Cell
Cells are the primary state units in CKB, within them users can include arbitrary states.
has a capacity limit, which represents both the CKB balance and how much data can be stored in the Cell simultaneously.

Transferring balance in CKB involves consuming some input Cells from the sender's account and producing new output Cells which can be unlocked by the receiver's account. The amount transferred is equal to the total capacity of the consumed Cells.

In this tutorial, you will learn how to write a simple dApp to transfer CKB balance from one account to another.

Setup Devnet & Run Example

Step 1: Clone the Repository

To get started with the tutorial dApp, clone the repository and navigate to the appropriate directory using the following commands:

git clone https://github.com/nervosnetwork/docs.nervos.org.git
cd docs.nervos.org/examples/simple-transfer

Step 2: Start the Devnet

To interact with the dApp, ensure that your Devnet is up and running. After installing @offckb/cli, open a terminal and start the Devnet with the following command:

offckb node

You might want to check pre-funded accounts and copy private keys for later use. Open another terminal and execute:

offckb accounts

Step 3: Run the Example

Navigate to your project, install the node dependencies, and start running the example:

yarn && NETWORK=devnet yarn start

Now, the app is running in http://localhost:1234


Behind the Scene

Open the lib.ts file in your project and check out the generateAccountFromPrivateKey function:

export const generateAccountFromPrivateKey = async (
privKey: string
): Promise<Account> => {
const signer = new ccc.SignerCkbPrivateKey(cccClient, privKey);
const lock = await signer.getAddressObjSecp256k1();
return {
lockScript: lock.script,
address: lock.toString(),
pubKey: signer.publicKey,
};
};

What this function does is generate the account's public key and address via a private key. Here, we need to construct and encode a Lock Script

to obtain the corresponding address of this account. A Lock Script ensures that only the owner can consume their Live Cells
A Cell that has not been consumed and is available for use.
.

Here, we use the CKB standard Lock Script template, combining the SECP256K1 signing algorithm with the BLAKE160 hashing algorithm, to build such a Lock Script. Note that different templates will yield different addresses when encoding the address, corresponding to different types of guard for the assets.

Once we have the Lock Script of an account, we can determine how much balance the account has. The calculation is straightforward: we query and find all the Cells that use the same Lock Script and sum all these Cells' capacities; the sum is the balance.

export async function capacityOf(address: string): Promise<bigint> {
const addr = await ccc.Address.fromString(address, cccClient);
let balance = await cccClient.getBalance([addr.script]);
return balance;
}
tip

In Nervos CKB, Shannon is the smallest currency unit, with 1 CKB = 10^8 Shannons. This unit system is similar to Bitcoin's Satoshis, where 1 Bitcoin = 10^8 Satoshis. In CCC SDK, the value handle are mostly done in the Shannon unit.

Next, we can start to transfer balance. Check out the transfer function in lib.ts:

export async function transfer(
toAddress: string,
amountInCKB: string,
signerPrivateKey: string
): Promise<string>;

The transfer function accepts parameters such as toAddress, amountInShannon, and signerPrivateKey to sign the transfer transaction.

This transfer transaction collects and consumes as many capacities as needed using some Live Cells as the input Cells and produce some new output Cells. The Lock Script of all these new Cells is set to the new owner's Lock Script. In this way, the CKB balance is transferred from one account to another, marking the transition of Cells from old to new.

Thanks to the CCC SDK, we can use high-level helper function ccc.Transaction.from to perform the transfer transaction, which wraps the above logic.

export async function transfer(
toAddress: string,
amountInCKB: string,
signerPrivateKey: string
): Promise<string> {
const signer = new ccc.SignerCkbPrivateKey(cccClient, signerPrivateKey);
const { script: toLock } = await ccc.Address.fromString(toAddress, cccClient);

// Build the full transaction to estimate the fee
const tx = ccc.Transaction.from({
outputs: [{ lock: toLock }],
outputsData: [],
});

// CCC transactions are easy to be edited
tx.outputs.forEach((output, i) => {
if (output.capacity > ccc.fixedPointFrom(amountInCKB)) {
alert(`Insufficient capacity at output ${i} to store data`);
return;
}
output.capacity = ccc.fixedPointFrom(amountInCKB);
});

// ....
}

Next, we need to complete the inputs of the transaction.

//....

// Complete missing parts for transaction
await tx.completeInputsByCapacity(signer);
await tx.completeFeeBy(signer, 1000);

Now we can use signer to sign and send the CKB transaction

// ...
const txHash = await signer.sendTransaction(tx);
console.log(
`Go to explorer to check the sent transaction https://pudge.explorer.nervos.org/transaction/${txHash}`
);

return txHash;

You can open the console on the browser to see the full transaction to confirm the process.


Congratulations!

By following this tutorial this far, you have mastered how balance transfers work on CKB. Here's a quick recap:

  • The capacity of a Cell indicates both the CKB balance and the amount of data that can be stored in the Cell simultaneously.
  • Transferring CKB balance involves transferring some Cells from the sender to the receiver.
  • We use ccc.Transaction.from from the CCC SDK to build the transfer transaction.

Next Step

So now your dApp works great on the local blockchain, you might want to switch it to different environments like Testnet and Mainnet.

To do that, just change the environment variable NETWORK to testnet:

export NETWORK=testnet

For more details, check out the README.md.

Additional Resources