Description
The CCC library's CellOutput.from() auto-capacity calculation only triggers when capacity === 0, but when a developer provides a non-zero capacity that's still too low, the library silently accepts it and the node rejects the transaction with InsufficientCellCapacity.
Error
InsufficientCellCapacity(Outputs[0]): expected occupied capacity (0x58d5e200) <= capacity (0x2540be400)
Root Cause
In @ckb-ccc/core/src/ckb/transaction.ts (lines 260-282), CellOutput.from() auto-calculates capacity:
if (output.capacity === Zero && outputData != null) {
output.capacity = fixedPointFrom(
output.occupiedSize + bytesFrom(outputData).length,
);
}
This only runs when capacity === 0. If a developer sets any non-zero value (even if too low), the library skips the calculation and passes the transaction through. The node then rejects it.
The actual occupied size formula (CellOutput.occupiedSize at line 232-233) is:
get occupiedSize(): number {
return 8 + this.lock.occupiedSize + (this.type?.occupiedSize ?? 0);
}
Where Script.occupiedSize (src/ckb/script.ts line 134) is:
get occupiedSize(): number {
return 33 + bytesFrom(this.args).length;
}
So the true minimum is: 8 + (33 + args) + data — but developers only account for data.
Reproduction
const outputs = [{
lock: lockScript,
capacity: 256n * 100_000_000n, // 256 CKB for 256 bytes of data
}];
await txBuilder.buildAndSend(outputs, [dataHex], privateKey);
// Fails: InsufficientCellCapacity
// Actual required: (8 + 33 + 20 + 256) = 317 CKB minimum
Expected Behavior
The library should validate capacity in CellOutput.from() regardless of whether it's zero or non-zero, and throw a clear error like:
Insufficient capacity: provided 256 CKB, minimum required 317 CKB
(8 struct + 53 lock + 256 data = 317 bytes)
Impact
- The auto-calculation exists but only triggers on
capacity === 0
- Any non-zero under-estimated value bypasses the check
- Developers consistently hit this because the common "1 CKB = 1 byte" mental model ignores the ~61 bytes of struct + script overhead
- The common workaround is hardcoding 500+ CKB buffers, wasting ~28% per cell or manually calculating it which you cant do because as a developer new to the system, i definitly wont account for those overheads.
Description
The CCC library's
CellOutput.from()auto-capacity calculation only triggers whencapacity === 0, but when a developer provides a non-zero capacity that's still too low, the library silently accepts it and the node rejects the transaction withInsufficientCellCapacity.Error
Root Cause
In
@ckb-ccc/core/src/ckb/transaction.ts(lines 260-282),CellOutput.from()auto-calculates capacity:This only runs when
capacity === 0. If a developer sets any non-zero value (even if too low), the library skips the calculation and passes the transaction through. The node then rejects it.The actual occupied size formula (
CellOutput.occupiedSizeat line 232-233) is:Where
Script.occupiedSize(src/ckb/script.tsline 134) is:So the true minimum is: 8 + (33 + args) + data — but developers only account for data.
Reproduction
Expected Behavior
The library should validate capacity in
CellOutput.from()regardless of whether it's zero or non-zero, and throw a clear error like:Impact
capacity === 0