Skip to main content
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 });

Complete inputs

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);
1

Describe outputs

Call ccc.Transaction.from with the outputs you want to create.
2

Collect inputs

Call tx.completeInputsByCapacity(signer) to pull in enough cells from the wallet.
3

Pay fees

Call tx.completeFeeBy(signer) to calculate and collect the network fee, and route change back to the signer.
4

Send

Call signer.sendTransaction(tx) to sign and broadcast. It returns the transaction hash.

Key method reference

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