Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Present a Verifiable Credential, signed by a did:cheqd Decentralized Identifier (DID), using Credo.
Verifiable Credentials signed by a did:cheqd can be securely presented using the AnonCreds proof format and the Present Proof Protocol v2 within the Credo framework. This enables trust-minimised, selective disclosure of credential data between a Holder and a Verifier.
Before presenting a credential:
A Verifiable Credential must have been issued and accepted by the Holder
A Credo Agent is running for both the Verifier and the Holder
A DIDComm connection exists between Holder and Verifier (via OOB or another method)
Both agents are configured with:
@credo-ts/anoncreds
@credo-ts/didcomm
Use any supported method to create a connection with the Holder. Automated is recommended. You can follow the same steps as described in .
After connection is established, the Verifier can send a proof request to the Holder.
Holder can get the stored credentials from own wallet and format a proof to send to the Verifier.
When we want to send a proof, we have to listen to incoming proof requests and handle accordingly. In this example we do not have any user interaction, but is likely that your application would have a user-interface which would display the request.
The Verifier sends a proof request with specific attributes and credential requirements
The Holder uses locally stored credentials to generate a selective disclosure proof
The proof is signed using AnonCreds and returned to the Verifier over DIDComm
The Verifier cryptographically validates the proof and the issuing DID (did:cheqd)
For more tutorials and examples, visit .
await this.agent.modules.proofs.requestProof({
protocolVersion: 'v2',
connectionId: connectionRecord.id,
proofFormats: {
anoncreds: {
name: 'proof-request',
version: '1.0',
requested_attributes: {
name: {
name: 'name',
restrictions: [
{
cred_def_id: this.credentialDefinition?.credentialDefinitionId,
},
],
},
},
},
},
})this.agent.events.on(ProofEventTypes.ProofStateChanged
async ({ payload }: ProofStateChangedEvent) => {
if (payload.proofRecord.state === ProofState.RequestReceived) {
const requestedCredentials = await this.agent.modules.proofs.selectCredentialsForRequest({
proofRecordId: payload.proofRecord.id,
})
await this.agent.modules.proofs.acceptRequest({
proofRecordId: proofRecord.id,
proofFormats: requestedCredentials.proofFormats,
})
}
})Deactivate a did:cheqd DID using the Credo Agent.
This guide explains how to deactivate a did:cheqd using a configured Credo Agent. Deactivating a DID marks it as no longer usable but does not remove it from the ledger — the DID can still be resolved and its deactivated state will be reflected in its metadata.
Before deactivating a DID, ensure that:
The Credo agent is configured with cheqd support
The DID you are deactivating was created and is controlled by your agent
You have access to the signing key used to authorize DID updates
Deactivated DIDs remain resolvable
DID resolvers will indicate that the DID is deactivated
The DID and its associated document become immutable and non-functional
Deactivation is permanent
The optional versionId parameter allows you to assign a custom version identifier to the deactivation transaction
Once deactivated, the DID cannot be updated, reactivated, or used for issuing credentials
You can still resolve the DID to verify its deactivated status via the deactivated: true metadata
Create Decentralized Identifiers (DIDs) on cheqd using Credo.
did
✅
The full did:cheqd identifier you wish to deactivate
options
❌
Optional settings, including a versionId to track the update
await agent.dids.deactivate({
did: 'did:cheqd:testnet:b84817b8-43ee-4483-98c5-f03760816411',
options: {
versionId: '3.0', // Optional: for tracking version history or audit purposes
},
})@credo-ts/cheqd✅ You’ve set up a cosmosPayerSeed for publishing to the cheqd network
✅ You're connected to the correct network (mainnet or testnet)
Credo supports two approaches to creating a DID:
Use when you want full control over the DID structure and already have key(s) in your wallet.
Use when you want Credo to create the DID Document from a key you specify in secret.
method*: cheqd
secret
options*
didDocument
First, generate a key pair:
Use that key to construct and register the DID Document:
📝 Make sure the
publicKeyBase58matches the key in your wallet. The DID will be written to thetestnetunless you specify"mainnet"inoptions.network.
If you don’t want to manually build a DID Document, you can let Credo handle it based on your input key type and ID.
🔐 Credo will generate the DID Document using the key referenced in
secretand publish it to the network.
Now that your DID is live on the cheqd network, try:
Below are a list of alternatives for creating cheqd DIDs.
Issue a JSON-LD Verifiable Credential, signed by a did:cheqd Decentralized Identifier (DID), using Credo.
Using the Issue Credential v2 Protocol, you can issue JSON-LD Verifiable Credentials signed by a did:cheqd identifier with just a few lines of code. This guide walks through the full flow using the Credo Agent.
Before you begin, ensure you have:
Basic knowledge of .
A cheqd testnet or mainnet account with sufficient tokens for DID operations
The issuer agent requires the cheqd module for DID operations and additional modules for W3C JSON-LD credential processing.
The holder agent needs to resolve cheqd DIDs and handle JSON-LD credentials.
Use any supported method to create a connection with the Holder of the credential. Automated is recommended.
The Issuer agent will create a new connection invite for the Holder. This is needed to securely communicate between the Issuer and the Holder agents.
The above request will have an invitation in the response. Holder will have to copy that invitation and pass URL as invitationUrl in the following code:
Both agents need event listeners to handle the credential exchange protocol automatically.
In this example, we will initiate the credential issuance process by having the holder propose a credential.
The Credential Acceptance and storage is handled automatically by the event listeners registered for both Issuer and Holder.
Resolve a DID-Linked Resource (DLR) on cheqd using the Credo Agent.
The resolveResource method in the Credo cheqd module enables you to retrieve DID-Linked Resources stored on the cheqd network. These resources can be resolved in two primary ways:
Use the full DID + /resources/<resourceId> format to directly resolve a known resource.
This approach is ideal when you know the exact UUID of the resource.
You can also resolve a resource using a DID URL query format with resourceName and resourceType parameters:
This method is useful when:
You want to resolve the latest version of a known logical resource
You don’t have or track the UUID
For a comprehensive list and detailed explanations of these parameters, refer to .
Issue and present SD-JWT VC credentials signed by cheqd Decentralized Identifiers (DIDs), using Credo.
SD-JWT Verifiable Credentials enable selective disclosure and unlinkability, combining the compact JWT format with privacy-preserving cryptographic techniques. They are optimized for privacy, mobile compatibility, and integration with modern identity standards.
In Credo, SD-JWT credentials are issued using OpenID for Verifiable Credential Issuance (OID4VCI) and presented using OpenID for Verifiable Presentations (OID4VP)—ensuring secure, interoperable credential exchange based on open standards.
✅ This approach is fully aligned with the EU Digital Identity Wallet standards and protocols, and the developing EU Architecture and Reference Framework (ARF).
Create an Credential Definition as a DID-Linked Resource on cheqd for issuing AnonCreds.
After registering a schema, a credential definition can be registered based on the schema. The credential definition, amongst more things, binds the schema to a specific issuer. Schemas can be reused between issuers, but a credential definition is specific to an issuer. In a credential definition revocation can also be specified. This section will not go in-depth about revocation.
The request body must contain the credential_definition object with the Issuer DID and the Schema ID created in the previous steps.
To enable revocation, the options
Credo SDK with cheqd support for Decentralized Identifiers (DIDs), DID-Linked Resources (DLRs) and Credentials.
Credo is a TypeScript-based framework for building Self-Sovereign Identity (SSI) agents and services. It is designed for modularity, interoperability, and compliance with the European Architecture and Reference Framework (ARF).
Previously known as Aries Framework JavaScript, Credo has recently been rebranded and transitioned from the Hyperledger Foundation to the Open Wallet Foundation, reflecting its broader mission and growing community.
Credo has evolved into a more flexible and general-purpose SSI framework. A major milestone in this evolution is the full
const key = await agent.wallet.createKey({
keyType: KeyType.Ed25519,
})
const ed25519PublicKeyBase58 = key.publicKeyBase58import { DidDocument } from '@credo-ts/core'
await agent.dids.create<CheqdDidCreateOptions>({
method: 'cheqd',
secret: {}, // No secret needed if key is already in wallet
options: {
network: 'testnet',
},
didDocument: new DidDocument({
id: 'did:cheqd:testnet:92874297-d824-40ea-8ae5-364a1ec9237d',
controller: ['did:cheqd:testnet:92874297-d824-40ea-8ae5-364a1ec9237d'],
verificationMethod: [
{
id: 'did:cheqd:testnet:92874297-d824-40ea-8ae5-364a1ec9237d#key-1',
type: 'Ed25519VerificationKey2018',
controller: 'did:cheqd:testnet:92874297-d824-40ea-8ae5-364a1ec9237d',
publicKeyBase58: ed25519PublicKeyBase58,
},
],
authentication: [
'did:cheqd:testnet:92874297-d824-40ea-8ae5-364a1ec9237d#key-1',
],
}),
})await agent.dids.create({
method: 'cheqd',
secret: {
verificationMethod: {
id: 'key-1', // Logical key name
type: 'Ed25519VerificationKey2020', // Or another supported type
},
},
options: {
network: 'testnet',
methodSpecificIdAlgo: 'uuid', // Optional: 'uuid' (default) or 'base58'
},
})Create DID-Linked Resource
Associate DID-Linked Resources to your DID using Credo.
Issue Verifiable Credentials
Issue Credentials using your cheqd DID using Credo.
Create DID
Create an Issuer DID with the did:cheqd DID method using Credo.
Update DID
Update a did:cheqd DID using Credo.
Deactivate DID
Deactivate a did:cheqd DID using Credo.
cheqd Studio
Our API product enables users to use cheqd's functionality with minimal complexity and easily integrate APIs into existing apps.
DID Registrar
Simple setup for building cheqd DIDs into existing applications using REST APIs, building into the Universal Registrar.
ACA-Py
ACA-Py plugin supports full cheqd support for DIDs as well as Verifiable Credentials.
Veramo
The Veramo SDK plugin supports JSON, JSON-LD credentials as well as cheqd Credential Payments in an SDK.
Walt.id Community Stack
Walt.id Community Stack is an SDK that supports the European Architecture and Reference Framework (ARF) standards for identity, with full cheqd support.
cheqd Cosmos CLI
Cosmos CLI which directly communicates with the cheqd network. This should only be used for testing environments.






npm install @credo-ts/core @credo-ts/node @credo-ts/askar @credo-ts/cheqd
npm install @hyperledger/aries-askar-nodejsimport type { InitConfig } from '@credo-ts/core'
import { AskarModule } from '@credo-ts/askar'
import {
Agent,
CredentialsModule,
V2CredentialProtocol,
JsonLdCredentialFormatService,
DidsModule,
HttpOutboundTransport,
WsOutboundTransport,
ProofsModule,
V2ProofProtocol,
DifPresentationExchangeProofFormatService,
CacheModule,
InMemoryLruCache,
W3cCredentialsModule,
KeyType,
DidDocumentBuilder,
utils,
getEd25519VerificationKey2018,
} from '@credo-ts/core'
import { agentDependencies, HttpInboundTransport } from '@credo-ts/node'
import { ariesAskar } from '@hyperledger/aries-askar-nodejs'
import { CheqdModule, CheqdModuleConfig, CheqdDidRegistrar, CheqdDidResolver } from '@credo-ts/cheqd'
let issuerDid: string
const issuerConfig: InitConfig = {
label: 'cheqd-jsonld-issuer',
walletConfig: {
id: 'cheqd-issuer-wallet',
key: 'testkey0000000000000000000000000',
},
}
const initializeIssuerAgent = async () => {
const issuer = new Agent({
config: issuerConfig,
dependencies: agentDependencies,
modules: {
askar: new AskarModule({ ariesAskar }),
dids: new DidsModule({
registrars: [new CheqdDidRegistrar()],
resolvers: [new CheqdDidResolver()],
}),
cheqd: new CheqdModule(
new CheqdModuleConfig({
networks: [
{
network: 'testnet', // or 'mainnet'
cosmosPayerSeed: 'your-cosmos-payer-seed-here',
},
],
})
),
credentials: new CredentialsModule({
credentialProtocols: [
new V2CredentialProtocol({
credentialFormats: [new JsonLdCredentialFormatService()],
}),
],
}),
proofs: new ProofsModule({
proofProtocols: [
new V2ProofProtocol({
proofFormats: [new DifPresentationExchangeProofFormatService()],
}),
],
}),
cache: new CacheModule({
cache: new InMemoryLruCache({ limit: 100 }),
}),
w3cCredentials: new W3cCredentialsModule({}),
},
})
// Register transports
issuer.registerOutboundTransport(new WsOutboundTransport())
issuer.registerOutboundTransport(new HttpOutboundTransport())
issuer.registerInboundTransport(new HttpInboundTransport({ port: 3001 }))
await issuer.initialize()
return issuer
}const holderConfig: InitConfig = {
label: 'cheqd-jsonld-holder',
walletConfig: {
id: 'cheqd-holder-wallet',
key: 'testkey0000000000000000000000000',
},
}
const initializeHolderAgent = async () => {
const holder = new Agent({
config: holderConfig,
dependencies: agentDependencies,
modules: {
askar: new AskarModule({ ariesAskar }),
dids: new DidsModule({
resolvers: [new CheqdDidResolver()],
}),
credentials: new CredentialsModule({
credentialProtocols: [
new V2CredentialProtocol({
credentialFormats: [new JsonLdCredentialFormatService()],
}),
],
}),
proofs: new ProofsModule({
proofProtocols: [
new V2ProofProtocol({
proofFormats: [new DifPresentationExchangeProofFormatService()],
}),
],
}),
cache: new CacheModule({
cache: new InMemoryLruCache({ limit: 100 }),
}),
w3cCredentials: new W3cCredentialsModule({}),
},
})
// Register transports
holder.registerOutboundTransport(new WsOutboundTransport())
holder.registerOutboundTransport(new HttpOutboundTransport())
holder.registerInboundTransport(new HttpInboundTransport({ port: 3002 }))
await holder.initialize()
return holder
}// Create a cheqd DID with Ed25519 verification method
const did = `did:cheqd:testnet:${utils.uuid()}`
const ed25519Key = await issuer.wallet.createKey({ keyType: KeyType.Ed25519 })
const createResult = await issuer.dids.create({
method: 'cheqd',
didDocument: new DidDocumentBuilder(did)
.addController(did)
.addVerificationMethod(
getEd25519VerificationKey2018({
key: ed25519Key,
controller: did,
id: `${did}#${ed25519Key.fingerprint}`,
})
)
.addAssertionMethod(`${did}#${ed25519Key.fingerprint}`)
.addAuthentication(`${did}#${ed25519Key.fingerprint}`)
.build(),
})
if (!createResult.didState.did) {
throw new Error('cheqd DID creation failed')
}
issuerDid = createResult.didState.did
console.log('Issuer DID created:', issuerDid)const createNewInvitation = async (agent: Agent) => {
const outOfBandRecord = await agent.modules.oob.createInvitation()
return {
invitationUrl: outOfBandRecord.outOfBandInvitation.toUrl({ domain: 'http://localhost:3001' }),
outOfBandRecord,
}
}const receiveInvitation = async (agent: Agent, invitationUrl: string) => {
const { outOfBandRecord } = await agent.modules.oob.receiveInvitationFromUrl(invitationUrl)
if (!outOfBandRecord) {
throw new Error(redText(Output.NoConnectionRecordFromOutOfBand))
}
return outOfBandRecord
}import { CredentialEventTypes, CredentialState } from '@credo-ts/core'
const setupCredentialListener = (agent: Agent) => {
agent.events.on(CredentialEventTypes.CredentialStateChanged, async ({ payload }) => {
const { credentialRecord } = payload
switch (credentialRecord.state) {
case CredentialState.ProposalReceived:
console.log('Issuer: Credential proposal received')
await agent.credentials.acceptProposal({
credentialRecordId: credentialRecord.id,
comment: 'JSON-LD Credential Offer',
})
break
case CredentialState.OfferReceived:
console.log('Holder: Credential offer received, accepting...')
await agent.credentials.acceptOffer({
credentialRecordId: credentialRecord.id,
credentialFormats: { jsonld: {} },
})
break
case CredentialState.RequestReceived:
console.log('Issuer: Credential request received, issuing credential...')
await agent.credentials.acceptRequest({
credentialRecordId: credentialRecord.id,
comment: 'JSON-LD Credential',
})
break
case CredentialState.CredentialReceived:
console.log('Holder: Credential received, accepting...')
await agent.credentials.acceptCredential({
credentialRecordId: credentialRecord.id,
})
break
case CredentialState.Done:
console.log('Done: Credential exchange completed!')
break
}
})
}// Define the credential to be proposed
const credentialOptions = {
credential: {
'@context': [
'https://www.w3.org/2018/credentials/v1',
'https://w3id.org/citizenship/v1',
'https://w3id.org/security/bbs/v1',
],
id: 'https://cheqd.io/credentials/permanent-resident-card',
type: ['VerifiableCredential', 'PermanentResidentCard'],
issuer: issuerDid,
issuanceDate: new Date().toISOString(),
expirationDate: new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1000).toISOString(), // 10 years
credentialSubject: {
id: 'did:example:holder123',
type: ['PermanentResident', 'Person'],
givenName: 'John',
familyName: 'Doe',
gender: 'Male',
birthDate: '1990-01-01',
birthCountry: 'United States',
residentSince: '2020-01-01',
lprNumber: '123-456-789',
lprCategory: 'IR1',
},
},
options: {
proofType: 'Ed25519Signature2018',
proofPurpose: 'assertionMethod',
},
}
// Get Connection
const holderConnection = await receiveInvitation(holderAgent, invitationUrl)
// Holder proposes credential
console.log('Starting credential proposal...')
const credentialExchange = await holderAgent.credentials.proposeCredential({
connectionId: holderConnection.id,
protocolVersion: 'v2',
credentialFormats: { jsonld: credentialOptions },
comment: 'Requesting Permanent Resident Card',
})
console.log('Credential exchange initiated:', credentialExchange.id)
// The rest of the flow is handled automatically by event listeners
// Wait for completion...await agent.modules.cheqd.resolveResource(
'did:cheqd:testnet:92874297-d824-40ea-8ae5-364a1ec9237d/resources/6de33634-6439-4e46-aa3f-bfe03606b000'
)await agent.modules.cheqd.resolveResource(
'did:cheqd:testnet:92874297-d824-40ea-8ae5-364a1ec9237d?resourceName=exampleTrustRegistry&resourceType=TrustRegistry'
)await agent.modules.cheqd.resolveResource(
'did:cheqd:testnet:<uuid>?resourceName=example&resourceType=exampleType&resourceVersionTime=2023-01-01T00:00:00Z'
)const credentialDefinitionResult = await agent.modules.anoncreds.registerCredentialDefinition({
credentialDefinition: {
tag: 'default',
issuerId: '<did>',
schemaId: schemaResult.schemaState.schemaId,
},
options: {
supportRevocation: false,
},
})
if (credentialDefinitionResult.credentialDefinitionState.state === 'failed') {
throw new Error(
`Error creating credential definition: ${credentialDefinitionResult.credentialDefinitionState.reason}`
)
}Issue and present SD-JWT credentials in Credo using the tutorials below:
SD-JWT credentials are ideal for ecosystems that require:
Privacy-preserving credential exchange Reveal only the claims you choose—no more, no less
Unlinkability across presentations Each presentation is cryptographically unique, preventing correlation by relying parties
Optimized for mobile and constrained environments Compact format, ideal for mobile wallets and low-bandwidth scenarios
Standards-compliant Built on IETF SD-JWT, OID4VCI, and OID4VP specifications
Interoperability with identity wallets Enables seamless interaction with both SSI wallets and federated identity providers supporting OpenID standards
EU Wallet-ready Fully compatible with the EU Digital Identity Wallet and ARF requirements
While SD-JWT focuses on minimal disclosure, it can integrate cheqd-native functionality through linked references:
🔗 DID-Linked Resources
Reference schemas, legal terms, or trust frameworks hosted on the cheqd ledger via out-of-band metadata
Use credential_metadata or presentation definitions to point to these resources
🏛️ Trust Registries Issuers can prove authorisation by referencing cheqd-based trust registries—linked via DID-Linked Resources or OpenID Federation metadata
🚫 Status Lists (Revocation) Revocation is supported via cheqd-compatible Bitstring Status Lists These can be referenced externally without compromising SD-JWT’s privacy guarantees
📦 These integrations maintain SD-JWT’s compact, privacy-first design while adding verifiability and governance via cheqd infrastructure.
Credo supports SD-JWT credential issuance and key binding using:
did:key
did:web
did:cheqd
Issuance: OID4VCI (OpenID for Verifiable Credential Issuance)
Presentation: OID4VP (OpenID for Verifiable Presentations)
Yarn or npm installed
A working Node.js or React Native project
Follow the Getting Started guide if you're new to Credo
To enable cheqd in your agent, install the required package:
This package provides everything needed to register, resolve, and interact with did:cheqd identifiers and AnonCreds objects.
If you're using React Native, additional steps are needed to support cosmjs:
Update your package.json using npm overrides or Yarn resolutions:
Example (NPM overrides):
Example (Yarn resolutions):
Install the buffer package:
Create a shim.js file:
Then, import this shim in your entry point (e.g. before your App.tsx is rendered):
After installing the dependencies, we can register the cheqd module on the agent by adding the below code snippet to the agent constructor.
The cosmosPayerSeed can be a 32-bit seed value or mnemonic. It can be managed using Leap wallet, which is available as a mobile app or browser extension for Chrome and Safari. Leap enables users to create accounts, exchange tokens, etc. To setup Leap wallet for cheqd follow the tutorial below:
Now that your Credo agent is successfully set up to work with cheqd, try following our tutorials for creating a new DID or issuing Verifiable Credentials.
This integration showcases Credo’s expanding support for:
Multiple credential formats
Diverse DID methods
Interoperable ledger technologies
Using Credo, developers can now issue Verifiable Credentials, create Credential Schemas, and define Credential Definitions using cheqd-native AnonCreds, enabled by the cheqd AnonCreds Object Method.
Credo may be the right choice as it has the following benefits:
TypeScript-native — strongly typed, developer-friendly, and production-ready
Modular and extensible — use only what you need, plug in new components
Cross-ledger compatibility — supports Indy, cheqd, and beyond
Flexible credential support — AnonCreds, W3C JSON-LD, JWT
Aligned with EU standards — interoperable with EU ARF and continuing to align with eIDAS 2.0 implementing acts
Backed by the Open Wallet Foundation — with growing community adoption
Credo is now the first framework to support AnonCreds on non-Indy ledgers, thanks to a deep integration with cheqd. ACA-Py has since followed suit.
This includes:
Issuance of Verifiable Credentials using cheqd DIDs
Creation of Credential Schemas and Credential Definitions
Support for the cheqd AnonCreds Object Method
Native DID and resource support for did:cheqd
Get setup with your Credo agent and begin using cheqd's functionality below:
Credo supports the following Credential formats:
Below are a list of enterprise applications that leverage Credo with full did:cheqd support under the hood:
Create cheqd DID-Linked Resources (DLRs) linked to cheqd Decentralized Identifiers (DIDs) using Credo.
DID-Linked Resources are digital files or data blobs that are permanently and verifiably linked to a specific did:cheqd identifier. Each resource is stored on the cheqd ledger and assigned a UUID, called the Resource ID, which acts as a unique, persistent reference.
Applications can retrieve these resources using either:
A fully-qualified Resource ID (DID + /resources/<UUID>)
Or a DID URL with query parameters like resourceName and resourceType
DID-Linked Resources enable storage of a wide variety of DID-associated metadata, including:
Trust Registry entries
Status Lists (e.g. credential revocation)
Logos and brand assets
Governance and policy documents
Below are a list of SDK alternatives for creating cheqd DID-Linked Resources.
Verify a SD-JWT Verifiable Credential, signed by a did:cheqd Decentralized Identifier (DID), using Credo and OpenID4VP.
Using the OpenID4VC module, you can verify OpenID compatible SD-JWT Credentials signed by a did:cheqd identifier. This guide walks through the flow of verification using the Credo Agent.
Before you being, ensure you have:
Basic knowledge of .
.
After setting the verifier as , configure the verifier as follows
Once you have configured the verifier, you can create an authorization request including an OpenID4VP presentation request based on . The authorization request method will generate an authorization request URI that you can share with a holder.
Add an event listener that listens for state changed events, this allows Verifier to know when the authorization session is complete.
Update a did:cheqd DID using the Credo Agent.
This guide walks you through how to update an existing cheqd DID using your configured Credo Agent. Updating a DID Document lets you add or modify keys, service endpoints, or other metadata associated with the DID.
Before you begin, make sure:
✅ Your Credo agent is already configured with cheqd support
✅ You have write access to the DID (i.e., the key used for DID creation or a controller key)
✅ You know the DID you want to update and its current document state
To update a did:cheqd, you must:
Fetch the current DID Document
Modify the relevant fields (e.g., add keys, update service endpoints)
Submit the updated DID Document using agent.dids.update(...)
Create an AnonCreds Schema as a DID-Linked Resource on cheqd for issuing AnonCreds.
To issue Verifiable Credentials, the issuer will have to first create a Schema and then a Credential Definition.
⚠️ Before you begin...
Make sure you've correctly configured the Credo agent with cheqd.
In order to register a schema and credential definition, a DID must be added to the agent first. This can be done by calling agent.dids.create(), but this does require an endorser DID to be present as the submitterDid. This is optional, if you have created a DID from the agent before as described .
When you have a registered (or imported) a DID on the network and in your wallet, you can register a schema. Registering a schema requires four fields: issuerId, name, version and attrNames. It is important to note that the issuerId must be the same as a DID in your wallet.
Create a DID-Linked Resource (DLR) on cheqd using the Credo Agent.
The createResource method from the Credo cheqd module allows you to create a DID-Linked Resource and publish it to the cheqd network under a did:cheqd identifier.
DID-Linked Resources are uniquely identified by a UUIDv4 resource ID and persistently attached to the DID on-chain. These resources can later be resolved by ID or query parameters (e.g., resourceName, resourceType).
Present a JSON-LD Verifiable Credential, signed by a did:cheqd Decentralized Identifier (DID), using Credo.
Verifiable Credentials signed by a did:cheqd can be securely presented using the Dif proof format and the Present Proof Protocol v2 within the Credo framework. This enables trust-minimised, selective disclosure of credential data between a Holder and a Verifier.
Before presenting a credential:
Issue, present and revoke AnonCreds signed by cheqd Decentralized Identifiers (DIDs), using Credo.
AnonCreds is a privacy-preserving Verifiable Credential format designed for selective disclosure, non-correlatable presentations, and revocation support. In Credo, AnonCreds is fully supported using the Issue Credential v1/v2 and Present Proof v1/v2 protocols.
Credo allows users to issue, hold, and verify AnonCreds credentials using a range of supported ledgers—including full integration with the cheqd network, a modern, scalable alternative to legacy Indy-based networks.
Receive and present a SD-JWT Verifiable Credential, signed by a did:cheqd Decentralized Identifier (DID), using Credo and OpenID4VP.
yarn add @credo-ts/cheqd
# or
npm install @credo-ts/cheqd{
"overrides": {
"@cosmjs/amino": "npm:@cosmjs-rn/amino@^0.27.1",
"@cosmjs/encoding": "npm:@cosmjs-rn/encoding@^0.27.1",
"@cosmjs/math": "npm:@cosmjs-rn/math@^0.27.1",
"@cosmjs/stargate": "npm:@cosmjs-rn/stargate@^0.27.1",
"@cosmjs/tendermint-rpc": "npm:@cosmjs-rn/tendermint-rpc@^0.27.1",
"@cosmjs/utils": "npm:@cosmjs-rn/utils@^0.27.1",
"@cosmjs/proto-signing": "npm:@cosmjs-rn/proto-signing@^0.27.1",
"@cosmjs/crypto": "npm:@cosmjs-rn/crypto@^0.27.1"
}
}{
"resolutions": {
"@cosmjs/amino": "npm:@cosmjs-rn/amino@^0.27.1",
"@cosmjs/encoding": "npm:@cosmjs-rn/encoding@^0.27.1",
"@cosmjs/math": "npm:@cosmjs-rn/math@^0.27.1",
"@cosmjs/stargate": "npm:@cosmjs-rn/stargate@^0.27.1",
"@cosmjs/tendermint-rpc": "npm:@cosmjs-rn/tendermint-rpc@^0.27.1",
"@cosmjs/utils": "npm:@cosmjs-rn/utils@^0.27.1",
"@cosmjs/proto-signing": "npm:@cosmjs-rn/proto-signing@^0.27.1",
"@cosmjs/crypto": "npm:@cosmjs-rn/crypto@^0.27.1"
}
}bashCopyEdityarn add buffertsCopyEditimport { Buffer } from 'buffer'
global.Buffer = BuffertsCopyEditimport './shim'import { Agent, DidsModule, KeyType, DidDocument } from '@credo-ts/core'
import { agentDependencies } from '@credo-ts/node'
import { AskarModule } from '@credo-ts/askar'
import { askar } from '@openwallet-foundation/askar-nodejs'
import {
ConnectionsModule,
V2ProofProtocol,
V2CredentialProtocol,
ProofsModule,
AutoAcceptProof,
AutoAcceptCredential,
CredentialsModule,
HttpOutboundTransport,
getDefaultDidcommModules,
} from '@credo-ts/didcomm'
import {
CheqdAnonCredsRegistry,
CheqdDidRegistrar,
CheqdDidResolver,
CheqdModule,
CheqdModuleConfig,
} from '@credo-ts/cheqd'
import { AnonCredsModule } from '@credo-ts/anoncreds'
import { anoncreds } from '@hyperledger/anoncreds-nodejs'
const agent = new Agent({
config,
dependencies: agentDependencies,
modules: {
dids: new DidsModule({
registrars: [new CheqdDidRegistrar()],
resolvers: [new CheqdDidResolver()],
}),
// AnonCreds
anoncreds: new AnonCredsModule({
registries: [new CheqdAnonCredsRegistry()],
anoncreds,
}),
// Add cheqd module
cheqd: new CheqdModule(
new CheqdModuleConfig({
networks: [
{
network: '<mainnet or testnet>',
cosmosPayerSeed: '<cosmos payer seed or mnemonic>',
},
],
})
),
// Indy VDR can optionally be used with Askar as wallet and storage implementation
askar: new AskarModule({
askar,
}),
connections: new ConnectionsModule({
autoAcceptConnections: true,
}),
credentials: new CredentialsModule({
autoAcceptCredentials: AutoAcceptCredential.ContentApproved,
credentialProtocols: [
new V2CredentialProtocol({
credentialFormats: [new LegacyIndyCredentialFormatService(), new AnonCredsCredentialFormatService()],
}),
],
}),
},
})Issue a Verifiable Credential
Issue SD-JWT Credentials signed by cheqd DIDs using Credo and OpenID4VCI.
Present a Verifiable Credential
Present SD-JWT Credentials signed by cheqd DIDs using Credo and OpenID4VP.
Verify a Verifiable Credential
Verify SD-JWT Credentials signed by cheqd DIDs using Credo and OpenID4VCI.
Leap Wallet
Manage your CHEQ natively through the Leap Wallet with full support on desktop, browser and mobile.
Create a DID
Create an Issuer DID with the did:cheqd DID method using Credo.
Issue a Verifiable Credential
Issue a Verifiable Credential (AnonCreds) using Credo signed by a did:cheqd DID.

did
✅
The DID you want to update
didDocument
✅
The complete updated DID Document
secret
⚠️
Required only if you're referencing new keys not yet in the agent’s wallet
options
❌
Optional advanced settings (e.g., method-specific configurations)
A Verifiable Credential must have been issued and accepted by the Holder
A Credo Agent is running for both the Verifier and the Holder
A DIDComm connection exists between Holder and Verifier (via OOB or another method)
Both agents are configured with Credo packages needed for JSON-LD Credential Proof.
Use any supported method to create a connection with the Holder. Automated out-of-band protocol is recommended. You can follow the same steps as described in Issue a Verifiable Credential.
Both agents need event listeners to handle the proof exchange protocol automatically.
Step 3: Send Proof Request
After the connection is established and event handlers registered, the Verifier can send a proof request to the Holder.
The Proof Acceptance and Presentation is handled automatically by the event listeners registered for both Verifier and Holder.
For more tutorials and examples, visit Credo Docs.
Configure the holder with cheqd and OpenID4VC Modules
This method:
Resolves the offer.
Accepts it via pre-authorized code flow.
Selects a binding method—did:key (preferred) or JWK—for the SD-JWT, depending on issuer capabilities
Once you have a credential in your wallet, you can present it by responding to a receive authorization request, which includes an OpenID4VP presentation request. This request can be generated either by the verifier module or an external OpenID4VC verifier. First, resolve the authorization request, then accept it to present the credential in your wallet.
// Create a verifier, assuming the agent is called 'verifier'
const openId4VcVerifier = await verifier.modules.openId4VcVerifier.createVerifier({})
// Create a did:key that we will use for signing OpenID4VP authorization requests
const verifierDidResult = await issuer.dids.create<KeyDidCreateOptions>({
method: 'key',
options: {
keyType: KeyType.Ed25519,
},
})
if (verifierDidResult.didState.state !== 'finished') {
throw new Error('DID creation failed.')
}
const verifierDidKey = DidKey.fromDid(verifierDidResult.didState.did)const { authorizationRequest, verificationSession } =
await verifier.modules.openId4VcVerifier.createAuthorizationRequest({
verifierId: openId4VcVerifier.verifierId,
requestSigner: {
didUrl: `${verifierDidKey.did}#${verifierDidKey.key.fingerprint}`,
method: 'did',
},
// Add DIF presentation exchange data
presentationExchange: {
definition: {
id: '9ed05140-b33b-445e-a0f0-9a23aa501868',
name: 'Employee Verification',
purpose: 'We need to verify your employee status to grant access to the employee portal',
input_descriptors: [
{
id: '9c98fb43-6fd5-49b1-8dcc-69bd2a378f23',
constraints: {
// Require limit disclosure
limit_disclosure: 'required',
fields: [
{
filter: {
type: 'string',
const: 'AcmeCorpEmployee',
},
path: ['$.vct'],
},
],
},
},
],
},
},
})// Listen and react to changes in the verification session
verifier.events.on<OpenId4VcVerificationSessionStateChangedEvent>(
OpenId4VcVerifierEvents.VerificationSessionStateChanged,
async (event) => {
if (event.payload.verificationSession.id === verificationSession.id) {
console.log('Verification session state changed to ', event.payload.verificationSession.state)
}
if (event.payload.verificationSession.state === OpenId4VcVerificationSessionState.ResponseVerified) {
const verifiedAuthorizationResponse = await verifier.modules.openId4VcVerifier.getVerifiedAuthorizationResponse(
verificationSession.id
)
console.log('Successfully verified presentation.', JSON.stringify(verifiedAuthorizationResponse, null, 2))
console.log('Exiting...')
process.exit()
}
}
)import { DidDocumentService } from '@credo-ts/core'
await agent.dids.update({
did: 'did:cheqd:testnet:b84817b8-43ee-4483-98c5-f03760816411',
// Used to authorize and derive additional keys, if needed
secret: {
verificationMethod: {
id: 'key-2',
type: 'JsonWebKey2020', // Can also be Ed25519VerificationKey2020, etc.
},
},
didDocument: {
id: 'did:cheqd:testnet:b84817b8-43ee-4483-98c5-f03760816411',
controller: ['did:cheqd:testnet:b84817b8-43ee-4483-98c5-f03760816411'],
verificationMethod: [
{
id: 'did:cheqd:testnet:b84817b8-43ee-4483-98c5-f03760816411#key-1',
type: 'Ed25519VerificationKey2020',
controller: 'did:cheqd:testnet:b84817b8-43ee-4483-98c5-f03760816411',
publicKeyMultibase: 'z6MknkzLUEP5cxqqsaysNMWoh8NJRb3YsowTCj2D6yhwyEdj',
},
],
authentication: [
'did:cheqd:testnet:b84817b8-43ee-4483-98c5-f03760816411#key-1',
],
service: [
new DidDocumentService({
id: 'did:cheqd:testnet:b84817b8-43ee-4483-98c5-f03760816411#website',
type: 'linkedDomains',
serviceEndpoint: 'https://rand.in',
}),
],
},
})const seed = TypedArrayEncoder.fromString(`<seed>`) // Secret seed. Should be kept secure in production!
const cheqdDid = 'did:cheqd:testnet:d37eba59-513d-42d3-8f9f-d1df0548b675' // Cheqd DID to be imported
await agent.dids.import({
did: cheqdDid,
overwrite: true,
privateKeys: [
{
privateKey: seed,
keyType: KeyType.Ed25519,
},
],
})const schemaResult = await agent.modules.anoncreds.registerSchema({
schema: {
attrNames: ['name', 'degree', 'date'],
issuerId: '<did>',
name: 'Example Schema to register',
version: '1.0.0',
},
options: {},
})
if (schemaResult.schemaState.state === 'failed') {
throw new Error(`Error creating schema: ${schemaResult.schemaState.reason}`)
}import { ProofEventTypes, ProofState } from '@credo-ts/core'
const setupProofListener = (agent: Agent) => {
agent.events.on(ProofEventTypes.ProofStateChanged, async ({ payload }) => {
const { proofRecord } = payload
switch (proofRecord.state) {
case ProofState.RequestReceived:
console.log('Holder: Proof request received, creating presentation...')
const requestedCredentials = await agent.proofs.selectCredentialsForRequest({
proofRecordId: proofRecord.id,
})
await agent.proofs.acceptRequest({
proofRecordId: proofRecord.id,
proofFormats: {
presentationExchange: {
credentials: requestedCredentials.proofFormats['presentation-exchange']?.credentials || {},
},
},
})
break
case ProofState.PresentationReceived:
console.log('Issuer: Presentation received, verifying...')
await agent.proofs.acceptPresentation({
proofRecordId: proofRecord.id,
})
break
case ProofState.Done:
console.log('Proof verification completed!')
const proof = await agent.proofs.getById(proofRecord.id)
console.log('Proof is valid:', proof.isVerified)
break
}
})
}// Define what we want to verify
const presentationDefinition = {
id: 'permanent-resident-verification',
input_descriptors: [
{
id: 'permanent-resident-card',
name: 'Permanent Resident Card',
purpose: 'Verify permanent resident status',
constraints: {
fields: [
{
path: ['$.type'],
filter: {
type: 'array',
contains: { const: 'PermanentResidentCard' },
},
},
{
path: ['$.credentialSubject.givenName'],
filter: { type: 'string' },
},
{
path: ['$.credentialSubject.familyName'],
filter: { type: 'string' },
},
{
path: ['$.credentialSubject.lprNumber'],
filter: { type: 'string' },
},
],
},
},
],
}
// Get Connection
const {invitationUrl, issuerConnection} = await createInvitation(issuerAgent)
console.log('Requesting proof verification...')
const proofExchange = await issuerAgent.proofs.requestProof({
connectionId: issuerConnection.id,
protocolVersion: 'v2',
proofFormats: {
presentationExchange: { presentationDefinition },
},
comment: 'Please present your Permanent Resident Card for verification',
})
console.log('Proof request sent:', proofExchange.id)
// The rest of the flow is handled automatically by event listenersimport { Agent, DidsModule } from '@credo-ts/core';
import { agentDependencies } from '@credo-ts/node';
import { CheqdModule } from '@credo-ts/cheqd';
import { OpenId4VcHolderModule } from '@credo-ts/openid4vc';
const holder = new Agent({
config,
dependencies: agentDependencies,
modules: {
dids: new DidsModule({
resolvers: [new CheqdDidResolver()],
}),
openId4VcHolderModule: new OpenId4VcHolderModule(),
},
});import { DidKey, KeyDidCreateOptions, getJwkFromKey } from '@credo-ts/core';
const resolved = await holder.modules.openId4VcHolder.resolveCredentialOffer(credentialOffer);
const credentials = await holder.modules.openId4VcHolder.acceptCredentialOfferUsingPreAuthorizedCode(
resolved,
{
credentialBindingResolver: async ({
supportedDidMethods,
keyType,
supportsAllDidMethods,
supportsJwk,
credentialFormat,
}) => {
if (supportsAllDidMethods || supportedDidMethods?.includes('did:key')) {
const didResult = await holder.dids.create<KeyDidCreateOptions>({
method: 'key',
options: { keyType },
});
const didKey = DidKey.fromDid(didResult.didState.did);
return {
method: 'did',
didUrl: `${didKey.did}#${didKey.key.fingerprint}`,
};
}
if (supportsJwk && credentialFormat === OpenId4VciCredentialFormatProfile.SdJwtVc) {
const key = await holder.wallet.createKey({ keyType });
return { method: 'jwk', getJwkFromKey(key) };
}
throw new Error('No binding method supported.');
},
}
);
// Store the received credentials
const records: Array<W3cCredentialRecord | SdJwtVcRecord> = []
for (const credential of credentials) {
if ('compact' in credential) {
const record = await holder.sdJwtVc.store(credential.compact)
records.push(record)
} else {
const record = await holder.w3cCredentials.storeCredential({
credential,
})
records.push(record)
}
}// resolved credential offer contains the offer, metadata, etc..
const resolvedRequest = await holder.modules.openId4VcHolderModule.resolveSiopAuthorizationRequest(authorizationRequest)
console.log(
'Resolved credentials for request', JSON.stringify(resolvedRequest.presentationExchange.credentialsForRequest, null, 2)
)
const presExchangeService = holder.dependencyManager.resolve(DifPresentationExchangeService)
// Automatically select credentials. In a wallet you could manually choose which credentials to return based on the "resolvedAuthorizationRequest.presentationExchange.credentialsForRequest" value
const selectedCredentials = presExchangeService.selectCredentialsForRequest(
resolvedRequest.presentationExchange.credentialsForRequest
)
// issuer only supports pre-authorized flow for now
const authorizationResponse = await holder.modules.openId4VcHolderModule.acceptSiopAuthorizationRequest({
authorizationRequest: resolvedRequest.authorizationRequest,
presentationExchange: {
credentials: selectedCredentials,
},
})
console.log('Submitted authorization response', JSON.stringify(authorizationResponse.submittedResponse, null, 2))
cheqd Cosmos CLI
Cosmos-native CLI for creating transactions on cheqd. Should be used for testing purposes only.
did
✅
The did:cheqd identifier under which the resource is created
id
✅
A UUID string that acts as the permanent resource ID
name
✅
A human-readable resource name
resourceType
✅
A short type identifier (e.g. TrustRegistry, Logo, StatusList)
💡 The
idfield must be a UUIDv4. You are responsible for generating and tracking this ID.
Ensure the id is a UUIDv4, generated using a reliable UUID library (e.g., uuid in Node.js)
Keep resourceName and resourceType descriptive but concise — they are used for resolution
If the resource data is sensitive or large, consider encoding as a base64 string
Use version to manage changes to the resource over time
AnonCreds credentials are purpose-built for high-assurance, privacy-respecting use cases that require:
Selective disclosure of individual claims
Zero-knowledge proofs
Revocation support via AnonCreds Status Lists
Credential definitions and revocation registries anchored on-ledger
Credo now supports did:cheqd for issuing AnonCreds credentials using the cheqd AnonCreds Object Method. This removes dependency on Indy-based networks while retaining the proven AnonCreds credential exchange flow.
✅ Register did:cheqd identifiers for use in credential issuance
✅ Publish Schemas and Credential Definitions as DID-Linked Resources
✅ Issue and verify AnonCreds credentials signed with did:cheqd
✅ Enable revocation using AnonCreds Status List Definitions and Entries, also stored on-ledger as DID-Linked Resources
✅ This allows developers to migrate off the deprecated Sovrin network or other Indy networks without changing their existing flows or protocols.
Issue AnonCreds credentials
✅ Yes
Present and verify credentials
✅ Yes
Revocation support via cheqd AnonCreds Status Lists
✅ Yes
DID-Linked Resource anchoring (schemas, defs, status)
✅ Yes
Use did:cheqd for AnonCreds
✅ Yes
Use other Indy-compatible ledgers
✅ Yes
Take a deep dive into the cheqd AnonCreds Object Method below:
Setup your Credo Agent
Setup the cheqd agent to start being able to use cheqd inside Credo.
Create a Decentralized Identifier (DID)
Use the cheqd DID module to start creating cheqd DIDs.
Create DID-Linked Resources (DLRs)
Create generic DID-Linked Resources, as well as schemas and credential definitions.
Issue a Verifiable Credential
Issue and present Verifiable Credentials using a cheqd DID.
AnonCreds
AnonCreds is a Zero-Knowledge Credential format, offering full Holder privacy and selective disclosure.
SD-JWT
Selective Disclosure JWT (SD-JWT) is the most commonly adopted credential format for European Digital Identity Ecosystems, allowing users to selectively disclose which attributes they would like to share in a presentation.
JSON-LD
JSON-LD (Linked Data) Credentials are a richer data format, allowing applications to follow embedded links to other pieces of Linked Data across the web.
Paradym (Animo Solutions)
Paradym by Animo Solutions fully supports EU Architecture & Reference Framework standards with cheqd under the hood.
Hovi Studio (Hovi)
Hovi provides an all-in-one platform for issuing and managing Verifiable Credentials with cheqd DIDs supported.
Sudo Platform (Anonyome Labs)
API-first developer platform for cheqd DIDs and support for multiple Verifiable Credential formats.



Issue a Verifiable Credential (AnonCreds), signed with a did:cheqd Decentralized Identifier (DID).
Using AnonCreds and the Issue Credential v2 Protocol, you can issue Verifiable Credentials signed by a did:cheqd identifier with just a few lines of code. This guide walks through the full flow using the Credo Agent.
Before you begin, make sure you have:
A registered did:cheqd for the Issuer.
A and already created and published as DID-Linked Resources.
A configured with:
@credo-ts/cheqd for DID operations and resource publishing.
@credo-ts/anoncreds for AnonCreds credential handling.
@credo-ts/didcomm
Two agents: an Issuer and a Holder (can be separate apps or run locally)
Secure connectivity between agents using Out-of-Band (OOB) or a supported connection method.
Use any supported method to create a connection with the Holder of the credential. Automated is recommended.
The Issuer agent will create a new connection invite for the Holder. This is needed to securely communicate between the Issuer and the Holder agents.
The above request will have an invitation in the response. Holder will have to copy that invitation and pass URL as invitationUrl in the following code:
Generate a credential offer and send to the holder, informing them about the available credential and its attributes.
When we want to accept a credential, we have to listen to incoming credentials and handle accordingly. In this example we do not have any user interaction, but is likely that your application would have a user-interface which would display the credential. When receiving a credential offer you can get the values from credentialExchangeRecord.credentialAttributes.
Issue Verifiable Credentials with Credo, signed by cheqd Decentralized Identifiers (DIDs).
Credo provides full support for working with Verifiable Credentials (VCs) and Verifiable Presentations (VPs), based on the standards defined by the W3C and the Aries RFCs.
Credo enables users to issue, hold, present, and verify credentials in a secure and interoperable way using DIDComm messaging and OpenID for Verifiable Credential protocols. This functionality forms the foundation of any Self-Sovereign Identity (SSI) ecosystem.
Get started issuing and presenting credentials with your Credo agent, either with AnonCreds, JSON-LD or SD-JWT VC:
Credo supports multiple credential formats and exchange protocols out of the box, including:
Alignment with the European Digital Identity Wallet initiative and standards.
Selective disclosure baked in at credential-format level.
Issuance and presentation support with OpenID4VCI and OpenID4VP.
Ideal for privacy-preserving use cases that require zero-knowledge proofs, selective disclosure, and non-revocation proofs
Backed by a credential definition and revocation registry stored on a supported ledger (e.g., cheqd, Indy)
Common in enterprise and government deployments
Standards-compliant with the W3C VC Data Model
Extensible for cheqd DID-Linked Resources and Trust Registries
Issuance and presentation over DIDComm v2.
Suitable for web-native and mobile-first use cases
Below are a list of alternatives for using Credentials with cheqd support. Each offers a different set of protocols and underlying technical capabilities.
Issue and present JSON-LD credentials signed by cheqd Decentralized Identifiers (DIDs), using Credo.
JSON-LD Verifiable Credentials follow the W3C Verifiable Credentials Data Model, enabling decentralized, semantically rich, and interoperable credential exchange.
In Credo, you can issue, verify, and present JSON-LD credentials over DIDComm v2, using the Issue Credential v2 and Present Proof v2 protocols—alongside support for Linked Data Proofs and various signature suites.
Issue and present JSON-LD credentials with Credo using the tutorials below:
JSON-LD credentials are ideal for ecosystems that require:
Standards-aligned, interoperable credentials
Richly structured, linked data
Flexible signature types (e.g. Ed25519, BBS+)
Integration with W3C-conformant wallets and identity providers
Thanks to the flexibility of the W3C VC data model, JSON-LD credentials can be extended to support advanced cheqd-native features, including:
Credentials can reference schemas, governance frameworks, or legal documents published as DID-Linked Resources on the cheqd ledger
Use the termsOfUse or credentialSchema fields to point to cheqd-hosted resources
A credential can reference a trust registry (also a DID-Linked Resource) that defines the rules, requirements, or authorisations under which the credential was issued
cheqd-compatible Bistring Status List entries can be referenced using the credentialStatus field, enabling scalable and private revocation
These status lists can also be stored as DID-Linked Resources on cheqd
🔧 These extensions follow W3C patterns and require no change to the base data model—making them portable, verifiable, and standards-compliant.
Credo natively supports signing JSON-LD credentials using DIDs such as:
did:key
did:web
did:cheqd
await agent.modules.cheqd.createResource(
'did:cheqd:testnet:92874297-d824-40ea-8ae5-364a1ec9237d',
{
id: '6de33634-6439-4e46-aa3f-bfe03606b000',
name: 'exampleTrustRegistry',
resourceType: 'TrustRegistry',
version: '1.0',
data: {
name: 'Example Org',
jurisdiction: 'EU',
},
}
)data
✅
The content to store (object, Base64-encoded string, or plain text)
version
❌
Optional semantic version string (e.g. "1.0")
alsoKnownAs
❌
Optional array of aliases for the resource
Issue AnonCreds Credentials
Issue AnonCreds Credentials signed by cheqd DIDs using Credo.
Present AnonCreds Credentials
Present AnonCreds Credentials using Credo.
cheqd AnonCreds Object Method
Understand how cheqd supports AnonCreds Objects using DID-Linked Resources.
Issue JSON-LD credentials
✅ Yes
Present and verify JSON-LD credentials
✅ Yes
Support for multiple proof types
✅ Yes (Ed25519Signature2018, Ed25519Signature2020, etc.)
Use of external or on-chain contexts
✅ Yes
Support for DID-Linked Resources
⚠️ Via extensions and custom fields
1
Create and accept a secure connection between Issuer and Holder
2
Issuer prepares and sends a credential offer (via AnonCreds v2)
3
Holder accepts and stores the credential automatically or via UI
const createNewInvitation = async (agent: Agent) => {
const outOfBandRecord = await agent.modules.oob.createInvitation()
return {
invitationUrl: outOfBandRecord.outOfBandInvitation.toUrl({ domain: 'http://localhost:3001' }),
outOfBandRecord,
}
}const receiveInvitation = async (agent: Agent, invitationUrl: string) => {
const { outOfBandRecord } = await agent.modules.oob.receiveInvitationFromUrl(invitationUrl)
if (!outOfBandRecord) {
throw new Error(redText(Output.NoConnectionRecordFromOutOfBand))
}
return outOfBandRecord
}await this.agent.modules.credentials.offerCredential({
connectionId: connectionRecord.id,
protocolVersion: 'v2',
credentialFormats: {
anoncreds: {
attributes: [
{
name: 'name',
value: 'Alice Smith',
},
{
name: 'degree',
value: 'Computer Science',
},
{
name: 'date',
value: '01/01/2022',
},
],
credentialDefinitionId: credentialDefinition.credentialDefinitionId,
},
},
})this.agent.events.on(CredentialEventTypes.CredentialStateChanged,
async ({ payload }: CredentialStateChangedEvent) => {
switch (payload.credentialRecord.state) {
case CredentialState.OfferReceived:
console.log('received a credential')
// custom logic here
await this.agent.modules.credentials.acceptOffer({ credentialRecordId: payload.credentialRecord.id })
break
case CredentialState.Done:
console.log(`Credential for credential id ${payload.credentialRecord.id} is accepted`)
// For demo purposes we exit the program here.
process.exit(0)
}
})Issue a SD-JWT Verifiable Credential, signed by a did:cheqd Decentralized Identifier (DID), using Credo and OpenID4VCI.
Using the OpenID4VC module, you can issue OpenID compatible SD-JWT Credentials signed by a did:cheqd identifier. This guide walks through the flow of issuance using the Credo Agent.
Before you being, ensure you have:
Basic knowledge of .
A cheqd testnet or mainnet account with sufficient tokens for DID operations.
Configure the issuer with cheqd and OpenID4VC Modules
This registers:
cheqd for did:cheqd DID creation and ledger integration
openId4VcIssuer and openId4VcVerifier for OID4VC flows
This declares support for:
vc+sd-jwt format
cryptographic binding with wasm-bound did:cheqd keys
If you want to change the display metadata or the credentials supported by the issuer, you can use the issuer.modules.openId4VcIssuer.updateIssuer method.
The core of the issuance process is the credentialRequestToCredentialMapper function. This function constructs the credential to be signed and issued to the Holder. It includes:
Domain-specific claims: e.g., employee ID, role.
Cryptographic bindings: Associations with the Holder.
Optional selective disclosure: Customisable elements for privacy control.
Utilising payload and disclosureFrame, you have full flexibility over the credential's structure and visibility. This allows for privacy-preserving credentials that adhere to the SD-JWT specification and the OpenID for Verifiable Credential Issuance (OID4VCI) standard.
This constructs a standard SD‑JWT payload—structural claims ready for selective disclosure.
With this expanded payload, you can also enhance your disclosureFrame:
This configuration means:
The lastName, role, evidence, and termsOfUse claims are blinded in the signed JWT.
The Holder can choose to reveal these fields when presenting the credential.
While SD-JWT allows for simple flat key-value claims, some fields from the full VC model (like @context, type, and deeply nested credentialSubject) are not directly represented due to JWT limitations. However, you can convey semantics through:
vct (type semantics)
credentialSchema (structure enforcement)
custom claim naming (e.g., credentialSubjectId instead of nesting)
Once you have configured the issuer, you can create a credential offer. The credential offer method will generate an offer URI that you can share with a holder.
We have also added an event listener that listens for state changed events, this allows Issuer to know when the issuance session is complete.
issuanceDate
iat
ISO 8601 format
expirationDate
exp
Optional
credentialSubject
Flattened into individual keys
SD-JWT doesn’t nest claims
evidence
evidence
Optional, can be array of structured info
credentialSchema
credentialSchema
Helps verifier interpret structure
termsOfUse
termsOfUse
Optional. Encodes policy using ODRL or similar—can include rights, duties, and prohibitions.
termsOfUse to express legal/policy frameworks around credential usage, such as prohibitions on sharing.@context
Omitted in SD-JWT
Context is not typically included in JWT payloads
id
id
Use urn:uuid:... or full URL
type
vct
Set via vct (Verifiable Credential Type)
issuer
issuer
Must be a valid DID
npm install @credo-ts/core @credo-ts/node @credo-ts/cheqd
npm install @credo-ts/openid4vcimport { Agent, DidsModule, KeyType } from '@credo-ts/core';
import { agentDependencies } from '@credo-ts/node';
import { CheqdModule } from '@credo-ts/cheqd';
import express, { Router } from 'express'
import { OpenId4VcIssuerModule, OpenId4VcVerifierModule } from '@credo-ts/openid4vc';
// Create two express routers, all endpoints for the
// issuer and verifier will be added to these routers
const verifierRouter = Router()
const issuerRouter = Router()
// Register the routers on the express server. The path should match
// with the baseUrl you configure in the modules below.
const app = express()
app.use('/oid4vci', issuerRouter)
app.use('/siop', verifierRouter)
const issuer = new Agent({
config,
dependencies: agentDependencies,
modules: {
dids: new DidsModule({
registrars: [new CheqdDidRegistrar()],
resolvers: [new CheqdDidResolver()],
}),
cheqd: new CheqdModule(
new CheqdModuleConfig({
networks: [
{
network: '<mainnet or testnet>',
cosmosPayerSeed: '<cosmos payer seed or mnemonic>',
},
],
})
),
openId4VcIssuer: new OpenId4VcIssuerModule({
baseUrl: 'https://your-issuer-host/oid4vci',
router: issuerRouter,
endpoints: {
// The credentialRequestToCredentialMapper is the only required endpoint
// configuration that must be provided. This method is called whenever a
// credential request has been received for an offer we created. The callback should
// return the issued credential to return in the credential response to the holder.
credential: {
// you'll map credential once requests come in
credentialRequestToCredentialMapper: async ({ credentialRequest }) => {
// See step 5.
},
},
},
}),
// openId4VcVerifier module can only be used in Node.js
openId4VcVerifier: new OpenId4VcVerifierModule({
baseUrl: 'https://your-issuer-host/siop',
router: verifierRouter,
}),
},
});
// listen on port 3000 for the openid4vc app.
app.listen(3000)// Create a did:cheqd that we will use for issuance
const issuerDidResult = await issuer.dids.create({
method: 'cheqd',
options: {
network: 'testnet',
methodSpecificIdAlgo: 'uuid',
},
})
if (issuerDidResult.didState.state !== 'finished') {
throw new Error('DID creation failed.')
}
const issuerDid = issuerDidResult.did;import { JwaSignatureAlgorithm } from '@credo-ts/core'
// Create an issuer with one supported credential: AcmeCorpEmployee
const openid4vcIssuer = await issuer.modules.openId4VcIssuer.createIssuer({
display: [
{
name: 'ACME Corp.',
description: 'ACME Corp. is a company that provides the best services.',
text_color: '#000000',
background_color: '#FFFFFF',
logo: {
url: 'https://acme.com/logo.png',
alt_text: 'ACME Corp. logo',
},
},
],
credentialsSupported: [
{
format: 'vc+sd-jwt',
vct: 'AcmeCorpEmployee',
id: 'AcmeCorpEmployee',
cryptographic_binding_methods_supported: ['did:cheqd'],
cryptographic_suites_supported: [JwaSignatureAlgorithm.ES256],
},
],
})const credentialRequestToCredentialMapper: OpenId4VciCredentialRequestToCredentialMapper = async ({
// agent context for the current wallet / tenant
agentContext,
// the credential offer related to the credential request
credentialOffer,
// the received credential request
credentialRequest,
// the list of credentialsSupported entries
credentialsSupported,
// the cryptographic binding provided by the holder in the credential request proof
holderBinding,
// the issuance session associated with the credential request and offer
issuanceSession,
}) => {
const firstSupported = credentialsSupported[0]
const { sub } = credentialRequest.claims;
const payload = {
vct: firstSupported.vct, // Verifiable Credential Type identifier
// Credential subject fields (flattened)
credentialSubjectId: sub, // Represents subject's DID (e.g., Holder DID)
firstName: 'John',
lastName: 'Doe',
employeeId: 'EMP-1234',
role: 'engineer',
// Optional: evidence and schema
evidence: [{
type: 'EmployeeRecord',
verifier: issuerDid,
evidenceDocument: 'HR Database Entry 2024-Q1',
subjectPresence: 'Physical',
documentPresence: 'Digital',
}],
credentialSchema: {
id: 'https://example.org/schemas/employee-passport.json',
type: 'JsonSchemaValidator2018',
},
// Credential Status
credentialStatus: {
id: 'https://status.cheqd.net/vc/123456',
type: 'StatusList2021Entry',
statusPurpose: 'revocation',
statusListIndex: '123456',
statusListCredential: 'https://status.cheqd.net/list/employee-vc.json',
},
// Timestamps in numeric format
notBefore: Math.floor(Date.now() / 1000),
expiry: Math.floor((Date.now() + 31536000000) / 1000),
// Terms of Use
termsOfUse: [
{
type: 'OdrlPolicy2017',
profile: 'https://cheqd.net/policies/employee-vc-policy.json',
prohibition: [
{
assigner: issuerDid,
target: 'credential',
action: 'share',
},
],
},
],
};
return {
credentialSupportedId: firstSupported.id,
format: 'vc+sd-jwt',
// We can provide the holderBinding as is, if we don't want to make changes
holder: holderBinding,
payload: payload,
disclosureFrame: {
_sd: ['lastName', 'credentialStatus', 'termsOfUse'],
},
issuer: {
method: 'cheqd',
issuerDid,
},
};
}tsCopyEditdisclosureFrame: {
_sd: ['lastName', 'role', 'evidence', 'termsOfUse'],
}const { credentialOffer, issuanceSession } =
await issuer.modules.openId4VcIssuer.createCredentialOffer({
issuerId: openid4vcIssuer.issuerId,
// values must match the `id` of the credential supported by the issuer
offeredCredentials: ['AcmeCorpEmployee'],
// Only pre-authorized code flow is supported
preAuthorizedCodeFlowConfig: {
userPinRequired: false,
},
// You can store any metadata about the issuance here
issuanceMetadata: {
someKey: 'someValue',
},
})
// Listen and react to changes in the issuance session
issuer.events.on<OpenId4VcIssuanceSessionStateChangedEvent>(
OpenId4VcIssuerEvents.IssuanceSessionStateChanged,
(event) => {
if (event.payload.issuanceSession.id === issuanceSession.id) {
console.log('Issuance session state changed to ',
event.payload.issuanceSession.state)
}
}
)