Guides

Node.js Backend

Use CCC for server-side CKB development, data analysis, and scripting.

Edit on GitHub

Use CCC from a Node.js server, script, or CI job — with a private key signer instead of a browser wallet. The server-side package @ckb-ccc/shell gives you the full CCC API (transactions, UDT, Spore, SSRI) without any browser or DOM dependencies.

What you'll get

After this guide you'll be able to:

  • Connect to mainnet / testnet from a Node.js process
  • Sign transactions with a private key for backend automation, airdrops, or indexing jobs
  • Query balances and iterate cells programmatically
  • Reuse the same transaction composition and message signing patterns from the frontend guides — the API is identical

Installation

npm install @ckb-ccc/shell

What it exports

@ckb-ccc/shell re-exports the following:

export * from "@ckb-ccc/core/barrel"; // full CCC core (Transaction, Script, Address, ...)
export { spore } from "@ckb-ccc/spore";
export { ssri } from "@ckb-ccc/ssri";
export { udt } from "@ckb-ccc/udt";

Import everything through the ccc namespace:

import { ccc } from "@ckb-ccc/shell";

Connect to a node

Use ClientPublicMainnet or ClientPublicTestnet for zero-config connectivity. Both default to a public RPC endpoint with automatic WebSocket / HTTP fallbacks — no setup required:

import { ccc } from "@ckb-ccc/shell";

const client = new ccc.ClientPublicMainnet();
// Defaults to wss://mainnet.ckb.dev/ws (Node.js with WebSocket)
// Falls back to https://mainnet.ckb.dev/ and https://mainnet.ckbapp.dev/
import { ccc } from "@ckb-ccc/shell";

const client = new ccc.ClientPublicTestnet();
import { ccc } from "@ckb-ccc/shell";

const client = new ccc.ClientPublicMainnet({
  url: "https://your-ckb-node.example.com/rpc",
});

Sign with a private key

Use this when: your backend needs to send transactions autonomously (e.g. airdrops, payouts, automated operations). SignerCkbPrivateKey provides a fully featured Signer backed by a raw secp256k1 private key.

import { ccc } from "@ckb-ccc/shell";

const client = new ccc.ClientPublicTestnet();
const signer = new ccc.SignerCkbPrivateKey(
  client,
  process.env.CKB_PRIVATE_KEY!, // 32-byte hex, loaded from environment
);

await signer.connect();
const address = await signer.getRecommendedAddress();
console.log("Address:", address); // "ckt1qz..." (testnet) or "ckb1qz..." (mainnet)

Never hardcode private keys in source code. Load them from environment variables, a secrets manager, or an encrypted key file. A leaked private key means permanent, irrecoverable loss of funds.

Query balance

Use this when: you need to check an account's CKB balance from a script or API handler.

import { ccc } from "@ckb-ccc/shell";

const client = new ccc.ClientPublicMainnet();
const signer = new ccc.SignerCkbPrivateKey(client, process.env.CKB_PRIVATE_KEY!);
await signer.connect();

const balance = await signer.getBalance();
// balance is a bigint in shannon (1 CKB = 100_000_000 shannon)
console.log(`Balance: ${ccc.fixedPointToString(balance)} CKB`); // e.g. "1234.56 CKB"

Complete example: query balance and send CKB

import { ccc } from "@ckb-ccc/shell";

async function main() {
  // 1. Connect to testnet
  const client = new ccc.ClientPublicTestnet();
  const signer = new ccc.SignerCkbPrivateKey(client, process.env.CKB_PRIVATE_KEY!);
  await signer.connect();

  const fromAddress = await signer.getRecommendedAddress();
  console.log("From:", fromAddress);

  // 2. Query balance
  const balance = await signer.getBalance();
  console.log(`Balance: ${ccc.fixedPointToString(balance)} CKB`);

  // 3. Resolve recipient address
  const recipientAddress = "ckt1qzda0cr08m85hc8jlnfp3sog3vczr9h9rqt4ykkqfzf3gcj…";
  const { script: lock } = await ccc.Address.fromString(
    recipientAddress,
    signer.client,
  );

  // 4. Build transaction
  const tx = ccc.Transaction.from({
    outputs: [{ capacity: ccc.fixedPointFrom(100), lock }],
  });

  // 5. Fill inputs and pay fee automatically
  await tx.completeInputsByCapacity(signer);
  await tx.completeFeeBy(signer);

  // 6. Sign and broadcast
  const txHash = await signer.sendTransaction(tx);
  console.log("Sent:", txHash);
}

main().catch(console.error);

Iterate cells

Use this when: you need to scan an address's live cells — for analytics, indexing, or building custom transaction logic.

for await (const cell of signer.findCellsOnChain(
  {}, // no type/data filter — return all cells
  true, // include cell data
)) {
  console.log(cell.outPoint, cell.cellOutput.capacity);
}

Advanced barrel

The package also exports an advanced entry point with lower-level utilities under the cccA namespace:

import { cccA } from "@ckb-ccc/shell/advanced";
// Includes sporeA (advanced Spore helpers) and core advanced utilities

TypeScript configuration

@ckb-ccc/shell ships ES modules. If you encounter ERR_REQUIRE_ESM or module resolution errors, add this to your tsconfig.json:

{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext"
  }
}

Troubleshooting

ERR_REQUIRE_ESM when importing @ckb-ccc/shell Your project is using CommonJS (require). Either switch to ESM ("type": "module" in package.json) or use dynamic import(). CCC does not ship a CJS build.

SignerCkbPrivateKey throws "invalid private key" The key must be exactly 32 bytes (64 hex characters), without 0x prefix. Double-check your environment variable.

ClientPublicTestnet connection times out Public nodes may be rate-limited. For production, run your own CKB node and pass its URL to ClientPublicMainnet({ url: "..." }).

I want to use browser-wallet signers (JoyID, MetaMask) on the server Those signers require a browser environment. On the server, use SignerCkbPrivateKey. If you need to verify wallet signatures on the server, use ccc.Signer.verifyMessage() — see Sign Messages.

Next steps

  • Compose transactions — the same declare → fill → fee → send pattern works with SignerCkbPrivateKey.
  • UDT tokens — issue and transfer fungible tokens from a backend.
  • Spore Protocol — mint on-chain digital objects programmatically.

On this page