I’ve been building Vellum, a reference dashboard for did:ckb, the Decentralized Identifier method defined in WIP-01 and implemented in web5fans/did-ckb. The dashboard talks directly to the deployed Type Script on mainnet and testnet, and the reusable bits live in a draft @ckb-ccc/identity package on a fork of ckb-devrel/ccc.
Sharing now because I’d like feedback on (a) the UX choices around DIDs on CKB, (b) the profile convention I added, and (c) whether the package belongs in CCC upstream.
What’s working end to end
Every operation in WIP-01 is wired against the deployed contract, plus the WIP-02 migration:
-
Claim: builds a transaction creating a DID Metadata Cell with the type-id-style identifier args, fills a DiceBear pixel-art default avatar seeded on the new DID, signs and submits, polls for inclusion.
-
Resolve: paste any did:ckb, get the document, profile, handles, verification methods, services. Reads directly from the indexer, no API in between.
-
My DID: reverse-lookup by Lock Script, hero card with the holder’s avatar and display name, document body with editable fields, Lock Script card, and the full on-chain operation history (walks the cell chain backwards through tx inputs and classifies each as CREATE / UPDATE / MIGRATE).
-
Edit: full document editor with inline validation hints for AT Protocol / Nostr /
did:keyshapes. -
Rotate: move control of a DID to a different CKB Lock without changing the identifier. Paste an address, the dashboard parses it via CCC’s
Address.fromString, shows old vs new side by side, signs with the current Lock. -
Deactivate: burn flow with a 24-hour UI cool-down per DID.
-
did:plc migration (WIP-02): fetches the source operation log from
plc.directory, signs the CKB tx hash with one of the holder’s PLC rotation keys (kept in browser memory only, never transmitted), attaches the witness inWitnessArgs.output_type, and surfaces the 72-hour finalisation window.
Notable design calls
A few things in the implementation that are worth flagging for discussion:
-
Profile convention. The DID document carries the
did:plc-compatible fields (verificationMethods,alsoKnownAs,services) plus aservices.profileentry of typeVellumProfilewithdisplayName,avatar,bioinline. Any resolver picks it up. Question for the community: useful enough to standardise across CKB ecosystem apps so the data is portable, or stay app-specific? -
Default avatar. When the holder doesn’t supply one, the SDK fills
https://api.dicebear.com/9.x/pixel-art/png?seed=<did>into the document at create time. Deterministic, identifies the holder visually without onboarding friction. -
Capacity reserve. Cell capacity is computed exactly and bumped by a 200 CKB reserve so the holder can grow the document later without re-funding. Most cells land between 300 and 600 CKB. Fully recoverable on deactivation.
-
History walk client-side. Implementing operation history without indexing infrastructure meant walking the cell chain backwards through tx inputs (
client.getTransaction+previousOutput). Cheap for typical DIDs, capped at 50 steps for safety.
The SDK
The package mirrors the conventions of @ckb-ccc/spore, @ckb-ccc/udt, etc: @ckb-ccc/core as a dep, ESM + CJS builds, an identity namespace export, optional /plc subpath for the migration helpers.
import {
resolveDid,
buildCreateTx,
buildUpdateTx,
buildDeactivateTx,
buildMigrationTx,
getDidHistory,
listDidsByLock,
} from "@ckb-ccc/identity";
import { fetchPlcLog } from "@ckb-ccc/identity/plc";
Lives on a feat/identity-package branch of my fork: ccc/packages/identity at feat/identity-package · truthixify/ccc · GitHub. Released as GitHub Release tarballs on the fork for now, because publishing under @ckb-ccc/* isn’t mine to do.
26 unit tests cover base32, identifier round-trips, document encoding, DAG-CBOR round-trip, default-avatar semantics. All passing under vitest.
Open question for CCC maintainers: does this belong upstream? Happy to follow whichever conventions are preferred. The PR would be small, one new package, no changes to anything existing.
What’s next, realistically
Directions I think make sense to explore. None are promised, all worth a conversation:
-
Spore integration. A Spore can carry
did:ckb:...in its metadata. Ownership stays with the Lock Script as today (Spores use Lock), but the issued-to binding can point to a DID, which means an NFT collection survives the holder’s wallet rotation. Worth prototyping. -
SBT-style badge cells keyed on DID args. An on-chain badge Cell with
args = DID identifier(20 bytes) is straightforward. Reputation systems (CKBoost is the obvious one) could anchor signals to a DID instead of an address, so reputation follows the user across key rotations. -
W3C Verifiable Credentials. Signed by a key in the DID’s
verificationMethodsmap, with the DID as subject. Stored off-chain by default, or pinned as a Cell when on-chain commitment matters. Standard pattern, just needs an issuance flow. -
Lock rotation via wallet connect. Today the rotate page accepts a pasted address. A nicer flow would let the holder connect a second wallet inline.
-
Resolver coverage. Today only did:ckb resolves directly through Vellum.
did:webanddid:keyare easy adds;did:plcis already reachable via the migration path.
I’m explicitly not committing to:
-
Merging the package into upstream CCC (depends on maintainers’ interest).
-
Publishing to npm under any official scope (would need permission first).
-
Spore / CKBoost / SBT integrations as part of this thread; flagging the direction, not promising the work.
Discussion prompts
Specific things I’d love community input on:
-
services.profileconvention. Worth standardising across CKB ecosystem apps so the data is portable, or fine app-specific? -
@ckb-ccc/identityupstream. Interest in merging this into ckb-devrel/ccc? Any API or scope changes maintainers would want before considering a PR? -
DID-anchored Spores / badges / reputation. Anyone interested in collaborating on a prototype? CKBoost folks especially.
-
DID method coverage. Should the resolver page handle other methods (
did:web,did:key,did:plcdirectly), or stay did:ckb-focused? -
WIP-04 Nostr profile. The dashboard doesn’t surface Nostr-specific fields yet (NIP-01 pubkey, NIP-05 handle, relay endpoints). Worth wiring as a first-class profile alongside
services.profile?
Links:
-
Live dashboard: https://vellum-lyart.vercel.app
-
Dashboard source: GitHub - truthixify/vellum: Dashboard for managing did:ckb identities. Your identity, on paper that lasts. · GitHub
-
SDK source: ccc/packages/identity at feat/identity-package · truthixify/ccc · GitHub
-
Spec: GitHub - web5fans/web5-wips: Web5 Improvement Proposals (WIPs) · GitHub
-
Contract: GitHub - web5fans/did-ckb · GitHub
Happy to answer anything in the thread.


