@wraith-protocol/sdk/chains/ckb.
Most developers should use the Agent Client instead. These primitives are for power users building custom stealth address integrations on CKB.
The Cell Model
CKB is fundamentally different from account-based chains like EVM or Solana. CKB uses a UTXO-based Cell model where all state is stored in Cells. A Cell has four fields:| Field | Purpose |
|---|---|
capacity | Amount of CKB stored (like a UTXO value) |
lock | Script that must be satisfied to spend (like an address/owner) |
type | Optional script for additional validation |
data | Arbitrary data |
Cells Are Announcements
On EVM chains, stealth address announcements are separate events emitted by an Announcer contract. On CKB, the Cell itself is the announcement. There is no separate announcer. The stealth lock scriptargs contain both the ephemeral public key and the stealth address hash:
Scanning = Querying Live Cells
Instead of querying a subgraph or parsing event logs, scanning on CKB means querying all live Cells that use thestealth-lock code hash. CKB’s built-in indexer RPC (get_cells) supports filtering by lock script code hash, so only stealth Cells are returned.
Spending = Consuming Cells
To withdraw from a stealth address, you consume the stealth Cell and create a new Cell at the destination. The stealth lock script verifies the secp256k1 signature against the pubkey hash in the args.Installation
fetch. Address hashing uses @noble/hashes (blake2b), which is already a direct dependency.
Import
Types
Key Differences from EVM
CKB uses the same secp256k1 curve as EVM but different hash functions and a completely different state model.| Aspect | EVM | CKB |
|---|---|---|
| Model | Account-based | UTXO (Cell) |
| Curve | secp256k1 | secp256k1 (same) |
| Announcement | Separate Announcer contract event | Embedded in Cell lock script args |
| Address hash | keccak256(pubkey)[12:32] | blake2b(pubkey)[0:20] (blake160) |
| Shared secret hash | keccak256(ECDH_shared) | SHA-256(ECDH_shared) |
| Scanning | Query subgraph for events | Query live Cells via get_cells RPC |
| Address format | 0x + 20-byte hex | bech32m CKB address |
| Min balance | None (EOA) | 61 CKB for cell capacity |
| Spending | sendTransaction | Consume Cell, create new Cell |
| View tags | Yes (fast rejection) | No (check every Cell) |
Constants
Functions
deriveStealthKeys(signature)
Derive spending and viewing key pairs from a 65-byte ECDSA signature. Identical to the EVM module.
r/s, keccak256 each to get spending and viewing keys.
generateStealthAddress(spendingPubKey, viewingPubKey, ephemeralKey?)
Generate a one-time stealth address with lock script args for CKB.
- Generate random ephemeral key pair
(r, R = r * G) - Compute ECDH shared secret
S = r * viewingPubKey(compressed) hashedSecret = SHA-256(S)— not keccak256 like EVMstealthPubKey = spendingPubKey + hashedSecret * GstealthPubKeyHash = blake160(stealthPubKey)— blake2b with “ckb-default-hash”, first 20 byteslockArgs = ephemeralPubKey || stealthPubKeyHash
blake160(data)
CKB’s address hashing function. blake2b with "ckb-default-hash" personalization, truncated to 20 bytes.
checkStealthCell(cell, viewingKey, spendingPubKey)
Check if a stealth Cell belongs to you.
- Extract
ephemeralPubKey = cell.lockArgs[0:33] - Compute ECDH shared secret
S = viewingKey * ephemeralPubKey hashedSecret = SHA-256(S)expectedPubKey = spendingPubKey + hashedSecret * GexpectedHash = blake160(expectedPubKey)- Compare
expectedHashwithcell.lockArgs[33:53]
get_cells RPC already filters to only stealth-lock Cells.
scanStealthCells(cells, viewingKey, spendingPubKey, spendingKey)
Scan an array of stealth Cells and return the ones that belong to you.
deriveStealthPrivateKey(spendingKey, ephemeralPubKey, viewingKey)
Compute the private key that controls a specific stealth Cell.
S = viewingKey * ephemeralPubKey(shared secret)hashedSecret = SHA-256(S)— not keccak256stealthPrivateKey = (spendingKey + hashedSecret) mod n
encodeStealthMetaAddress(spendingPubKey, viewingPubKey)
Encode two public keys into a CKB stealth meta-address string.
st:ckb:{spendingPubKeyHex}{viewingPubKeyHex} — 132 hex chars (two 33-byte compressed secp256k1 keys), same structure as EVM.
decodeStealthMetaAddress(metaAddress)
Decode a CKB meta-address back into its component public keys.
End-to-End Flow
CKB-Specific Considerations
- Minimum capacity: A stealth-lock Cell requires at least 61 CKB due to the 53-byte args. Senders must send at least this amount.
- No view tags: Every Cell must be fully checked. CKB’s
get_cellsRPC already filters by lock script code hash, so only stealth Cells are examined. - blake2b personalization: CKB uses
"ckb-default-hash"as the blake2b personalization parameter. This must be included or hashes won’t match on-chain verification. - UTXO spending: Withdrawing means consuming the Cell and creating a new one at the destination. The transaction fee is deducted from the Cell’s capacity.
- No names initially:
.wraithname registration is not available on CKB at launch. Names registered on other chains still resolve cross-chain via the agent.
Chain Deployments
getDeployment(chain)
Supported Networks
| Network | Status |
|---|---|
| CKB Testnet (Pudge) | Live |
Fetching Stealth Cells
fetchStealthCells(chain?)
Fetches all live stealth Cells from CKB using the get_cells RPC method, filtered by the stealth-lock code hash. Handles pagination automatically.

