Docs

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.

1 · Install

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.

package.json (Credo-ts 0.6.x)
"@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",
package.json (Credo-ts 0.5.x)
"@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",
2 · Configure the module

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.

agent.ts
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!,
}),
},
})
3 · Mint the issuer DID

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.

did.ts
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…
4 · Register a schema

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.

schema.ts
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 },
})
5 · Issue a credential

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.

issue.ts
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 } },
})
6 · Verify a presentation

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.

verify.ts
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.
7 · Revoke a credential

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.

revoke.ts
import { KanonAnonCredsRegistry } from '@ajna-inc/kanon'
const registry = agent.dependencyManager.resolve(KanonAnonCredsRegistry)
await registry.revokeCredentialOnChain(
agentContext,
credDefId,
credId,
)
Reference

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.

hash.ts
import { kanonCredIdHash } from '@ajna-inc/kanon'
// keccak256 of the utf-8 encoding
kanonCredIdHash('a3f4-…-9d2b')
// '0x4a…b2'
Source

Repositories.

Everything that ships kanon is open source under Apache-2.0. Pick the layer you need.

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.