| name | ckb-ccc-transactions |
| description | Covers composing and sending CKB transactions with CCC — building transaction outputs, completing inputs and fees, adding cell dependencies, and querying the chain (cells by lock, balance, transaction confirmation). Use when the user asks how to build, compose, or send a transaction, add cell deps, or query on-chain data — even if they just say "transfer CKB" or "check this transaction" without naming CCC methods. UDT and Spore transfers build on this pattern; see ckb-ccc-udt / ckb-ccc-spore for those specifics. Requires a connected Signer — see ckb-ccc-signer-setup if one hasn't been created yet. |
| metadata | {"author":"ckb-devrel","version":"1.0.0","role":"spoke","depends-on":"ckb-ccc-fundamentals, ckb-ccc-signer-setup","priority":"normal"} |
CKB CCC — Transactions
The canonical CCC transaction pattern. This applies to plain CKB transfers and is the foundation that ckb-ccc-udt and ckb-ccc-spore build on top of — those skills only cover what's different for their asset type, not the full pattern again.
You need a connected Signer before any of this — see ckb-ccc-signer-setup.
Standard transaction composition pattern
- Resolve recipient —
const { script: lock } = await ccc.Address.fromString(toAddress, signer.client).
- Build transaction —
const tx = ccc.Transaction.from({ outputs: [{ capacity: ccc.fixedPointFrom("100"), lock }] }).
- Complete inputs —
await tx.completeInputsByCapacity(signer) — must come before fee calculation.
- Pay fee —
await tx.completeFeeBy(signer) — omit fee rate argument to use automatic network rate.
- Send —
const txHash = await signer.sendTransaction(tx).
- Verify — Optionally wait for confirmation:
await signer.client.waitTransaction(txHash, 1).
This order is mandatory: outputs declared → completeInputsByCapacity → completeFeeBy → sendTransaction.
Calling completeFeeBy before completeInputsByCapacity produces an incorrect fee.
Cell Dep Management
await tx.addCellDepsOfKnownScripts(client, ccc.KnownScript.XUdt);
tx.addCellDeps({ outPoint: { txHash: "0x...", index: 0 }, depType: "depGroup" });
signer.prepareTransaction() (called automatically inside sendTransaction) adds
deps for the signer's own lock script. Do not add them again manually.
Querying the Chain
for await (const cell of client.findCellsByLock(lockScript, null, true)) {
console.log(cell.outPoint, cell.cellOutput.capacity);
}
await client.waitTransaction(txHash, 1);
const balance = await signer.getBalance();
Gotchas (transaction-specific)
| Symptom / Error | Cause | Fix |
|---|
"not enough capacity" | Total input capacity doesn't cover outputs + fee | Fund the address, or confirm completeInputsByCapacity is called before completeFeeBy so CCC collects enough inputs automatically |
| Transaction rejected by node | Fee too low or missing | Call completeFeeBy after completeInputsByCapacity; if a second argument (explicit feeRate) is passed, check it's not below 1000n (the node's default min_fee_rate) — bump to 2000n/4000n if rejections persist. Omit the argument entirely to let CCC calculate the network rate automatically. |
addCellDepsOfKnownScripts is not a function | Wrong import or outdated version | Use @ckb-ccc/shell not @ckb-ccc/core for backend; update CCC to latest |
Hallucination Guard
- ❌
new ccc.Transaction() — always use ccc.Transaction.from({ outputs: [...] })
- ❌ Manually computing fees — always use
completeFeeBy
- ❌ Manually selecting input cells — always use
completeInputsByCapacity
- ❌ Calling
completeFeeBy before completeInputsByCapacity — order is mandatory
- ❌
tx.addCellDepsOfKnownScripts without await — it's async
Checklist (transaction-specific)
Also check the cross-cutting checklist in ckb-ccc-fundamentals.