i'm trying to issue the credential from my cloud agent to the mobile agent running Aries-Bifold.
inside my cloud agent i'm using:
const anonCredsCredentialExchangeRecord =
await cloudAgent?.agent?.credentials.offerCredential({
protocolVersion: "v2",
connectionId: connectionId,
credentialFormats: {
anoncreds: {
credentialDefinitionId: cloudAgent?.credentialDefinitionId,
attributes: [
{ name: "full_name", value: fullName },
{ name: "date_of_birth", value: dateOfBirth },
{ name: "address", value: address },
{ name: "government_id", value: governmentID },
{ name: "contact_info", value: contactInfo },
],
},
},
});
but i'm getting this error:
Error issuing credential: AriesFrameworkError: Unable to create offer. No supported formats
at V2CredentialProtocol.createOffer (C:\\Users\\Tosat\\Desktop\\Ladon\\LadonCloudAgent\\node_modules@aries-framework\\core\\src\\modules\\credentials\\protocol\\v2\\V2CredentialProtocol.ts:334:13)
at CredentialsApi.offerCredential (C:\\Users\\Tosat\\Desktop\\Ladon\\LadonCloudAgent\\node_modules@aries-framework\\core\\src\\modules\\credentials\\CredentialsApi.ts:266:58)
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
\[cause\]: undefined
}
also, i'm getting the error from visual studio code saying
Type 'string' is not assignable to type 'never'.ts(2322)
CredentialsApiOptions.d.ts(46, 5): The expected type comes from property 'protocolVersion' which is declared here on type 'OfferCredentialOptions<[]>'
on line
protocolVersion: "v2",
this is my schema:
import * as initConfigurationData from "./configurationData.json";
export const governmentDigitalCredentialSchema = {
attrNames: ['full_name', 'date_of_birth', 'address', 'government_id', 'contact_info'],
issuerId: initConfigurationData.BCOVRIN_TEST_STARTOFTHECOMPLETEID + initConfigurationData.DID, // it means->did:indy:bcovrin:test:BYisj4xqWijKxoMrvR1KZW
name: 'Digital Identity Schema for Government ServicesXYZ',
version: '1.0.0',
};
This is my complete class code snippet for the cloud agent:
import {
Agent,
ConsoleLogger,
KeyDerivationMethod,
LogLevel,
HttpOutboundTransport,
WsOutboundTransport,
ConnectionsModule,
DidsModule,
OutOfBandRecord,
ConnectionEventTypes,
DidExchangeState,
ConnectionStateChangedEvent,
KeyDidResolver,
TypedArrayEncoder,
KeyType,
DidRecord,
} from "@aries-framework/core";
import { agentDependencies, HttpInboundTransport } from "@aries-framework/node";
import { AskarModule } from "@aries-framework/askar";
import { ariesAskar } from "@hyperledger/aries-askar-nodejs";
import { anoncreds } from "@hyperledger/anoncreds-nodejs";
import { AnonCredsModule } from "@aries-framework/anoncreds";
import { AnonCredsRsModule } from "@aries-framework/anoncreds-rs";
import { indyVdr } from "@hyperledger/indy-vdr-nodejs";
import {
IndyVdrAnonCredsRegistry,
IndyVdrIndyDidRegistrar,
IndyVdrIndyDidResolver,
IndyVdrModule,
} from "@aries-framework/indy-vdr";
import {
CheqdAnonCredsRegistry,
CheqdDidRegistrar,
CheqdDidResolver,
CheqdModule,
CheqdModuleConfig,
} from "@aries-framework/cheqd";
import { connect } from "ngrok";
import { BCOVRIN_TEST_GENESIS } from "./bc_ovrin";
import * as initConfigurationData from "./configurationData.json";
import { governmentDigitalCredentialSchema } from "./GovernmentDigitalCredentialSchema";
class LadonCloudAgent {
agent: Agent | undefined;
outOfBandRecord: OutOfBandRecord | undefined;
invitationUrl: string | undefined;
// Add this property to store the credential definition id
credentialDefinitionId: any;
connectionIds: any;
schemaId: any;
schema: any;
constructor() {
this.connectionIds = []; // Initialize an empty array to store connection IDs
// Call the initialize method in the constructor
this.initializeAgent().catch((error) => {
console.error(
"Error initializing LadonCloudAgent Inside CONSTRUCTOR:",
error
);
});
}
async initializeAgent() {
const config = await this.setupAgentConfiguration();
this.agent = this.createAgentInstance(config);
this.registerTransports();
await this.initializeAgentInstance();
//await this.createAndImportDID();
this.printAllDIDs();
await this.registerSchema();
await this.registerCredentialDefinition();
return this.agent;
}
async setupAgentConfiguration() {
const endpoint =
initConfigurationData.endpointURL ||
(await connect(initConfigurationData.agentPort));
return {
label: initConfigurationData.label,
logger: new ConsoleLogger(LogLevel.info),
connectionImageUrl: initConfigurationData.connectionImageUrl,
walletConfig: {
id: initConfigurationData.id,
key: initConfigurationData.walletPassword,
keyDerivationMethod: KeyDerivationMethod.Argon2IMod,
},
endpoints: [endpoint],
};
}
private createAgentInstance(config: any) {
return new Agent({
config,
modules: {
dids: new DidsModule({
registrars: [new CheqdDidRegistrar(), new IndyVdrIndyDidRegistrar()],
resolvers: [
new CheqdDidResolver(),
new KeyDidResolver(),
new IndyVdrIndyDidResolver(),
],
}),
anoncredsRs: new AnonCredsRsModule({
anoncreds,
}),
anoncreds: new AnonCredsModule({
// Here we add an Indy VDR registry as an example, any AnonCreds registry can be used
registries: [
new CheqdAnonCredsRegistry(),
new IndyVdrAnonCredsRegistry(),
],
}),
indyVdr: new IndyVdrModule({
indyVdr,
networks: [
{
isProduction: false,
indyNamespace: "bcovrin:test",
genesisTransactions: BCOVRIN_TEST_GENESIS,
connectOnStartup: true,
},
],
}),
// Add cheqd module
cheqd: new CheqdModule(
new CheqdModuleConfig({
networks: [
{
network: initConfigurationData.cheqdNetwork,
cosmosPayerSeed: initConfigurationData.seedPhrase24WordsKeplr,
},
],
})
),
connections: new ConnectionsModule({ autoAcceptConnections: true }),
// Register the Askar module on the agent
askar: new AskarModule({ ariesAskar }),
},
dependencies: agentDependencies,
});
}
private registerTransports() {
this.agent?.registerOutboundTransport(new WsOutboundTransport());
this.agent?.registerOutboundTransport(new HttpOutboundTransport());
this.agent?.registerInboundTransport(
new HttpInboundTransport({ port: initConfigurationData.agentPort })
);
}
private async initializeAgentInstance() {
try {
await this.agent?.initialize();
console.log("Agent initialized!");
} catch (error) {
console.error("Error initializing agent:", error);
throw error;
}
}
async createInvitation() {
if (!this.agent) {
throw new Error("Agent is not initialized.");
}
this.outOfBandRecord = await this.agent.oob.createInvitation();
return {
invitationUrl: this.outOfBandRecord.outOfBandInvitation.toUrl({
domain: "",
}),
outOfBandRecord: this.outOfBandRecord,
};
}
setupConnectionListener(
outOfBandRecord: OutOfBandRecord, // Add this parameter
cb: () => void
) {
if (!this.agent) {
throw new Error("Agent is not initialized.");
}
if (!outOfBandRecord) {
// Use the parameter here
throw new Error("There is no outOfBandRecord initialized.");
}
if (!outOfBandRecord.id) {
// Use the parameter here
throw new Error("There is no outOfBandRecord id initialized.");
}
this.agent.events.on<ConnectionStateChangedEvent>(
ConnectionEventTypes.ConnectionStateChanged,
({ payload }) => {
if (!outOfBandRecord) {
// Use the parameter here
throw new Error("There is no outOfBandRecord initialized.");
}
if (!outOfBandRecord.id) {
// Use the parameter here
throw new Error("There is no outOfBandRecord id initialized.");
}
if (payload.connectionRecord.outOfBandId !== outOfBandRecord.id) return;
if (payload.connectionRecord.state === DidExchangeState.Completed) {
console.log(
`Connection for out-of-band id ${outOfBandRecord.id} completed`
);
// Store the connection ID in the array
this.connectionIds.push(payload.connectionRecord.id);
console.log("connections array content:");
console.log(this.connectionIds);
// Custom business logic can be included here
// In this example we can send a basic message to the connection, but
// anything is possible
cb();
}
}
);
}
async registerSchema() {
try {
if (!this.agent) {
console.error("Agent is not initialized.");
return;
}
// Check if the schema already exists
this.schemaId =
governmentDigitalCredentialSchema.issuerId +
"/anoncreds/v0/SCHEMA/" +
governmentDigitalCredentialSchema.name +
"/" +
governmentDigitalCredentialSchema.version;
const existingSchema = await this.agent.modules.anoncreds.getSchema(
this.schemaId
);
console.log(existingSchema);
if (existingSchema) {
console.log(`Schema with ID ${this.schemaId} already exists.`);
this.schema = existingSchema;
return;
}
// Register the schema
const schemaResult = await this.agent.modules.anoncreds.registerSchema({
schema: governmentDigitalCredentialSchema,
options: {},
});
if (schemaResult.schemaState.state === "failed") {
throw new Error(schemaResult.schemaState.reason);
}
this.schema = schemaResult;
this.schemaId = schemaResult.schemaState.schemaId;
} catch (error) {
console.error("Error registering schema", error);
throw error;
}
}
async registerCredentialDefinition() {
if (!this.agent) {
console.error("Agent is not initialized.");
return;
}
// Register the credential definition if not already registered
const credentialDefinitionResult =
await this.agent.modules.anoncreds.registerCredentialDefinition({
credentialDefinition: {
tag: initConfigurationData.CredentialDefinitionTag,
issuerId:
initConfigurationData.BCOVRIN_TEST_STARTOFTHECOMPLETEID +
initConfigurationData.DID,
schemaId: this.schemaId,
},
options: {},
});
if (
credentialDefinitionResult.credentialDefinitionState.state === "failed"
) {
throw new Error(
`Error creating credential definition: ${credentialDefinitionResult.credentialDefinitionState.reason}`
);
}
// Store the credential definition id in the class property
//this.credentialDefinitionId =
//credentialDefinitionResult.credentialDefinitionState.credentialDefinitionId;
this.credentialDefinitionId =
credentialDefinitionResult.credentialDefinitionState.credentialDefinitionId
console.log(
"Credential definition registered (simone):",
this.credentialDefinitionId
);
/*
Here's an example of what a credentialDefinitionId might look like after registering a credential definition using the AnonCreds module in Hyperledger Aries:
NcYxiDXkpYi6ov5FcYDi1e:3:CL:18:TAG1
In this example:
NcYxiDXkpYi6ov5FcYDi1e is the issuer DID (Decentralized Identifier) that represents the entity issuing the credential.
3 is the credential definition version.
CL indicates the method used to create the credential definition. In this case, "CL" refers to Camenisch-Lysyanskaya.
18 is the schema sequence number, which helps identify the specific schema associated with the credential definition.
TAG1 is a tag or alias that helps identify the credential definition more easily.
*/
}
/*async createAndImportDID() {
const didExists = await this.checkIfDIDExists();
if (!didExists) {
const unqualifiedIndyDid = initConfigurationData.DID;
const indyDid = `did:indy:bcovrin:test:${unqualifiedIndyDid}`;
let did;
let privateKey;
try {
const data = fs.readFileSync("did_private_key.json", "utf-8");
const { savedDid, savedPrivateKey } = JSON.parse(data);
did = savedDid;
privateKey = savedPrivateKey;
} catch (error) {
const seed = TypedArrayEncoder.fromString(initConfigurationData.DIDSeed);
privateKey = seed;
const data = JSON.stringify({ savedDid: indyDid, savedPrivateKey: seed });
fs.writeFileSync("did_private_key.json", data, "utf-8");
}
if (!this.agent) {
throw new Error("Agent is not initialized.");
}
await this.agent.dids.import({
did: did || indyDid,
overwrite: true,
privateKeys: [
{
privateKey: privateKey,
keyType: KeyType.Ed25519,
},
],
});
}
}
async checkIfDIDExists() {
if (!this.agent) return false;
const didRecords = await this.agent.dids.getCreatedDids();
const unqualifiedIndyDid = initConfigurationData.DID;
const indyDid = `did:indy:bcovrin:test:${unqualifiedIndyDid}`;
return didRecords.some((record) => record.did === indyDid);
}
*/
async printAllDIDs() {
if (!this.agent) {
throw new Error("Agent is not initialized.");
}
const didRecords = await this.agent.dids.getCreatedDids();
console.log("All DIDs:");
for (const record of didRecords) {
console.log(`DID: ${record.did}`);
console.log(`Verkey: ${record.id}`);
console.log(`Role: ${record.role}`);
console.log(`Metadata: ${JSON.stringify(record.metadata)}`);
console.log("------------");
}
}
}
export default LadonCloudAgent;
and this is my route file:
import { Router, Request, Response } from "express";
import qrcode from "qrcode";
const routes = Router();
routes.get("/", async (req: Request, res: Response) => {
try {
const cloudAgent = req.agentInstance;
// Data to be passed to the rendered template
const renderedData = {
agentIsInitialized: cloudAgent?.agent?.isInitialized ?? false,
};
console.log("cloudAgent?.credentialDefinitionId: -------------")
console.log(cloudAgent?.credentialDefinitionId)
// Use the 'res.render()' method to send the 'index.ejs' file as the response
// Provide the correct path to the 'views' folder relative to the project's root
// Pass the data object as the second argument to the 'res.render()' method
res.render("index.ejs", renderedData);
} catch (error) {
console.error("Error initializing agent:", error);
res.status(500).json({ error: "Something went wrong" });
}
});
routes.get("/credential-issue-endpoint", (req, res) => {
try {
// Retrieve the list of connections
const connections = req.agentInstance?.connectionIds;
// Render the credential-issue.ejs template and pass the connections data
res.render("credential-issue.ejs", { connections });
} catch (error) {
console.error("Error rendering credential-issue:", error);
res.status(500).json({ error: "Something went wrong" });
}
});
routes.get("/generateDynamicQRCode", async (req, res) => {
try {
const cloudAgent = req.agentInstance;
// Create a new invitation using the agent
const invitationData = await cloudAgent?.createInvitation();
if (!invitationData) {
return res.status(500).json({ error: "Failed to create invitation" });
}
const { invitationUrl, outOfBandRecord } = invitationData;
// Generate a QR code from the invitation URL
const qrCodeDataURL = await qrcode.toDataURL(invitationUrl);
// Set up the connection listener for the new out-of-band record
cloudAgent?.setupConnectionListener(outOfBandRecord, () => {
console.log(
"We now have an active connection to use in the following tutorials"
);
});
// Send the QR code data URL as the response
res.json({ qrCodeDataURL });
} catch (error) {
console.error("Error creating dynamic invitation:", error);
res.status(500).json({ error: "Something went wrong" });
}
});
routes.post("/issue-credential-from-ejs-form", async (req, res) => {
try {
const cloudAgent = req.agentInstance;
const connectionId = req.body.connectionId;
const fullName = req.body.fullName;
const address = req.body.address;
const dateOfBirth = req.body.dateOfBirth;
const governmentID = req.body.governmentID;
const contactInfo = req.body.contactInfo;
//console.log(cloudAgent?.credentialDefinitionId?.schemaId)
// Use the connectionId and attribute values to issue the credential
const anonCredsCredentialExchangeRecord =
await cloudAgent?.agent?.credentials.offerCredential({
connectionId: connectionId,
protocolVersion: "v2",
credentialFormats: {
anoncreds: {
attributes: [
{ name: "full_name", value: fullName },
{ name: "date_of_birth", value: dateOfBirth },
{ name: "address", value: address },
{ name: "government_id", value: governmentID },
{ name: "contact_info", value: contactInfo },
],
credentialDefinitionId: cloudAgent?.credentialDefinitionId,
},
},
});
if (!anonCredsCredentialExchangeRecord) {
return res.status(500).json({ error: "Failed to offer credential" });
}
// check the state of the credential exchange
// and log appropriate messages or take further actions.
console.log("Credential offer sent successfully");
console.log(
"Credential exchange ID:",
anonCredsCredentialExchangeRecord.id
);
console.log(
"Credential exchange state:",
anonCredsCredentialExchangeRecord.state
);
res.redirect("/"); // Redirect back to the main page
} catch (error) {
console.error("Error issuing credential:", error);
res.status(500).json({ error: "Something went wrong" });
}
});
export default routes;
Thanks in advance for the help!
I tried checking the values that i put inside "await cloudAgent?.agent?.credentials.offerCredential()" but they seem to be right so i have no clue about where is the error.
I fixed it by adding this to my modules:
credentials: new CredentialsModule({
credentialProtocols: [
new V2CredentialProtocol({
credentialFormats: [new LegacyIndyCredentialFormatService(), new AnonCredsCredentialFormatService()],
}),
],
}),