CKB uses a cell model (an evolution of Bitcoin’s UTXO model). Every piece of on-chain state lives in a cell. A cell has:
- capacity — the amount of CKB shannons it holds (1 CKB = 10⁸ shannons)
- lock script — who can spend it (authorization)
- type script (optional) — what rules it must follow (validation)
- data (optional) — arbitrary bytes
A transaction consumes existing cells as inputs and produces new cells as outputs. The Transaction class in CCC is a builder that lets you describe what you want, then fills in the missing pieces automatically.
Create a transaction
Use Transaction.from to construct a transaction from a plain object. You only need to specify the outputs you want — inputs and fees come later:
import { ccc } from "@ckb-ccc/ccc";
const tx = ccc.Transaction.from({
outputs: [
{ capacity: ccc.fixedPointFrom(100), lock: recipientLock },
],
});
ccc.fixedPointFrom(100) converts a human-readable CKB amount into shannons (100n * 10n ** 8n).
You can pass initial inputs, cell deps, or witnesses if you already know them:
const tx = ccc.Transaction.from({
cellDeps: [...],
inputs: [...],
outputs: [...],
outputsData: [...],
witnesses: [...],
});
Add outputs
After construction, add more outputs with addOutput:
// Capacity is calculated automatically from the lock script size
tx.addOutput({ lock: recipientLock }, "0x");
// Provide an explicit capacity
tx.addOutput({ capacity: ccc.fixedPointFrom(50), lock: recipientLock });
Once you know what outputs the transaction must produce, call completeInputsByCapacity to collect enough input cells from the signer’s wallet to cover the output capacity:
await tx.completeInputsByCapacity(signer);
This iterates the signer’s live cells, adding them as inputs until the total input capacity meets or exceeds the total output capacity. It throws ErrorTransactionInsufficientCapacity if the wallet does not have enough CKB.
Pay fees
After inputs are complete, call completeFeeBy to calculate the required fee, add a change output back to the signer’s address, and collect additional inputs if needed:
await tx.completeFeeBy(signer);
You can pass an explicit fee rate (shannons per 1000 bytes) to override the network estimate:
await tx.completeFeeBy(signer, 1000n);
Fee calculation accounts for the full serialized transaction size, including witnesses. The change output automatically goes to the signer’s recommended address.
Add cell deps
Scripts referenced by a transaction’s inputs or outputs must be listed as cell deps so the CKB VM can load them. Use addCellDepsOfKnownScripts to look them up by name:
await tx.addCellDepsOfKnownScripts(
client,
ccc.KnownScript.Secp256k1Blake160,
ccc.KnownScript.XUdt,
);
Signers that handle known lock scripts (JoyID, OmniLock, etc.) add the required cell deps automatically during signTransaction / sendTransaction, so you typically only need to add deps for type scripts you introduce yourself.
Full transfer example
The following example is taken directly from the CCC examples package. It sends 100 CKB to the signer’s own recommended address:
import { ccc } from "@ckb-ccc/ccc";
// The receiver is the signer itself — resolve the address string
const receiver = await signer.getRecommendedAddress();
// Parse the receiver's lock script from the address
const { script: lock } = await ccc.Address.fromString(receiver, signer.client);
// Describe the desired output
const tx = ccc.Transaction.from({
outputs: [{ capacity: ccc.fixedPointFrom(100), lock }],
});
// Fill inputs to cover output capacity
await tx.completeInputsByCapacity(signer);
// Add a change output and pay the network fee
await tx.completeFeeBy(signer);
// Sign and broadcast
const txHash = await signer.sendTransaction(tx);
console.log("Transaction sent:", txHash);
Describe outputs
Call ccc.Transaction.from with the outputs you want to create.
Collect inputs
Call tx.completeInputsByCapacity(signer) to pull in enough cells from the wallet.
Pay fees
Call tx.completeFeeBy(signer) to calculate and collect the network fee, and route change back to the signer.
Send
Call signer.sendTransaction(tx) to sign and broadcast. It returns the transaction hash.
Key method reference
| Method | Description |
|---|
Transaction.from(txLike) | Construct a transaction from a plain object |
tx.addOutput(outputLike, data?) | Append an output cell |
tx.addInput(inputLike) | Append an input cell manually |
tx.addCellDeps(...deps) | Add cell dependencies |
tx.addCellDepsOfKnownScripts(client, ...scripts) | Add cell deps by KnownScript enum |
tx.completeInputsByCapacity(signer) | Auto-collect inputs to cover outputs |
tx.completeFeeBy(signer, feeRate?) | Auto-pay fees and produce change |
Always call completeInputsByCapacity before completeFeeBy. Fee calculation depends on the final transaction size, which is not known until inputs (and their witnesses) are present.