Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
did:cheqdawait 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'
)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()],
}),
],
}),
},
})
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
},
})options must contain the revocation flag, and the size of the revocation registry. Also, ensure that a Tails Server is configured for the issuer.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'
},
})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}`)
}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}`
)
}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,
})
}
})
Issue a Verifiable Credential (AnonCreds), signed with a did:cheqd Decentralized Identifier (DID).
Issue and present SD-JWT VC credentials signed by cheqd Decentralized Identifiers (DIDs), using Credo.
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',
}),
],
},
})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',
},
}
)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)
}
})Receive and present a SD-JWT Verifiable Credential, signed by a did:cheqd Decentralized Identifier (DID), using Credo and OpenID4VP.
import { 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))// 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 { 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 listenersIssue a JSON-LD Verifiable Credential, signed by a did:cheqd Decentralized Identifier (DID), using Credo.
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...Issue a SD-JWT Verifiable Credential, signed by a did:cheqd Decentralized Identifier (DID), using Credo and OpenID4VCI.
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)
}
}
)