Plug Kanon into a Credo agent in five minutes.
The plugin registers the Kanon AnonCreds VDR, wraps the verifier service for on-chain status checks, and subscribes to issuance events. You write zero AnonCreds plumbing.
Add the plugin to your backend.
@ajna-inc/kanon ships builds for both Credo 0.5.x and 0.6.x. Install the line that matches your agent — both share the same public API. The package works with any EVM RPC — Besu, Ethereum mainnet, L2s, private rollups. You need ethers v6.
"@credo-ts/anoncreds": "^0.6.1","@credo-ts/core": "^0.6.1","@credo-ts/didcomm": "^0.6.1","@ajna-inc/kanon": "^0.6.3","@ajna-inc/kanon-sdk": "^0.1.6","ethers": "^6.13.0",
"@credo-ts/anoncreds": "^0.5.17","@credo-ts/core": "^0.5.17","@ajna-inc/kanon": "^0.5.2","@ajna-inc/kanon-sdk": "^0.1.6","ethers": "^6.13.0",
Register KanonModule with your AnonCredsModule.
Pass the KanonAddressBook contract address — the plugin resolves the seven registry proxies off it. KanonModule MUST come after AnonCredsModule so its AnonCredsVerifierService override (which ANDs the on-chain status check) wins. Register KanonDidRegistrar + KanonDidResolver through DidsModule so agent.dids.create({ method: 'kanon' }) dispatches.
import { Agent, DidsModule } from '@credo-ts/core'import { AnonCredsModule } from '@credo-ts/anoncreds'import {KanonModule,KanonAnonCredsRegistry,KanonDidRegistrar,KanonDidResolver,} from '@ajna-inc/kanon'const agent = new Agent({config: { /* … */ },modules: {anoncreds: new AnonCredsModule({registries: [new KanonAnonCredsRegistry()],}),dids: new DidsModule({registrars: [new KanonDidRegistrar()],resolvers: [new KanonDidResolver()],}),kanon: new KanonModule({rpcUrl: process.env.KANON_RPC_URL!,privateKey: process.env.KANON_PRIVATE_KEY!,addressBook: process.env.KANON_ADDRESS_BOOK,issuerOrgId: process.env.KANON_ISSUER_ORG_ID!,}),},})
One-time per org.
Org-scoped DIDs are 'did:kanon:org:0x<orgId>'. The registrar performs the on-chain registration and stores the DID record in the agent. Re-running the call against the same orgId is a no-op.
const { didState } = await agent.dids.create({method: 'kanon',scope: 'org',orgId: process.env.KANON_ISSUER_ORG_ID!, // 0x<64 hex> bytes32})const issuerDid = didState.did! // did:kanon:org:0x…
Write your attrNames. The plugin handles the rest.
Pass your normal AnonCreds schema to buildKanonSchema. The plugin transparently adds the bookkeeping attribute needed for on-chain status lookup — you never type it.
import { buildKanonSchema } from '@ajna-inc/kanon'await agent.modules.anoncreds.registerSchema({schema: buildKanonSchema({name: 'DriverLicense',version: '1.0',attrNames: ['fullName', 'dateOfBirth', 'licenseNumber'],issuerId: issuerDid,}),options: { supportRevocation: false },})
Pass your attributes. The plugin adds the credId.
buildKanonCredentialAttributes prepends a fresh UUID for the bookkeeping field. The event listener writes issueCredential(credDefId, credIdHash) when the credential reaches state 'done' — you don't touch the chain.
import { buildKanonCredentialAttributes } from '@ajna-inc/kanon'const { attributes } = buildKanonCredentialAttributes([{ name: 'fullName', value: 'Jane Doe' },{ name: 'dateOfBirth', value: '1990-04-12' },{ name: 'licenseNumber', value: 'NY-441-AAA' },])await agent.modules.anoncreds.acceptCredentialOffer({credentialRecordId,credentialFormats: { anoncreds: { attributes } },})
Wrap your proof request. Get on-chain status for free.
buildKanonProofRequest adds the bookkeeping attribute to your proof request for each Kanon credDef. The wrapped AnonCredsVerifierService then runs anoncreds-rs and the on-chain isRevoked lookup — both must pass for isValid to be true.
import { buildKanonProofRequest } from '@ajna-inc/kanon'const proofRequest = buildKanonProofRequest({name: 'age check',version: '1.0',requested_attributes: {fullName: { name: 'fullName', restrictions: [{ cred_def_id }] },},kanonCredDefIds: [cred_def_id],})// verifyProof returns true only if both the AnonCreds CL proof// and the on-chain status lookup pass.
One transaction. No tails file.
Call revokeCredentialOnChain on the registry. The issuer's signer must be a member of the credDef's issuing org. The next verifier check returns 'revoked' on the very next call.
import { KanonAnonCredsRegistry } from '@ajna-inc/kanon'const registry = agent.dependencyManager.resolve(KanonAnonCredsRegistry)await registry.revokeCredentialOnChain(agentContext,credDefId,credId,)
kanonCredIdHash — internal.
Internal hash used as the on-chain registry key. The helpers above call it for you; you'll only need this if you're building tooling that talks to the contract directly.
import { kanonCredIdHash } from '@ajna-inc/kanon'// keccak256 of the utf-8 encodingkanonCredIdHash('a3f4-…-9d2b')// '0x4a…b2'
Repositories.
Everything that ships kanon is open source under Apache-2.0. Pick the layer you need.
- didkanon/contracts — Solidity registries + Hardhat + circom
- didkanon/sdk — TypeScript SDK (published as @ajna-inc/kanon-sdk)
- didkanon/credo-ts-kanon — Credo-ts plugin (published as @ajna-inc/kanon; tags
credo-5+credo-6) - didkanon/aca-py-did-kanon — ACA-Py plugin for the Python issuer side
- kanon-react-native — React-Native distribution with mobile polyfill notes
Need the optional ZK mode?
Mode B replaces the AnonCreds presentation with a Groth16 SNARK for unlinkable verification. See the architecture page for the circuit and verifier registry.