Files
This commit is contained in:
parent
45d524369b
commit
9955743801
|
|
@ -0,0 +1,87 @@
|
|||
<h1 align="center"><b>DEMO</b></h1>
|
||||
|
||||
This is the Credo demo. Walk through the Credo flow yourself together with agents Alice and Faber.
|
||||
|
||||
Alice, a former student of Faber College, connects with the College, is issued a credential about her degree and then is asked by the College for a proof.
|
||||
|
||||
## Features
|
||||
|
||||
- ✅ Creating a connection
|
||||
- ✅ Offering a credential
|
||||
- ✅ Requesting a proof
|
||||
- ✅ Sending basic messages
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Platform Specific Setup
|
||||
|
||||
In order to run the Credo demo, you need to make sure you have Node.JS and PNPM installed. See the [Credo Prerequisites](https://credo.js.org/guides/getting-started/prerequisites) for more information.
|
||||
|
||||
### Run the demo
|
||||
|
||||
These are the steps for running the Credo demo:
|
||||
|
||||
Clone the Credo git repository:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/openwallet-foundation/credo-ts.git
|
||||
```
|
||||
|
||||
Open two different terminals next to each other and in both, go to the demo folder:
|
||||
|
||||
```sh
|
||||
cd credo-ts/demo
|
||||
```
|
||||
|
||||
Install the project in one of the terminals:
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
```
|
||||
|
||||
In the left terminal run Alice:
|
||||
|
||||
```sh
|
||||
pnpm alice
|
||||
```
|
||||
|
||||
In the right terminal run Faber:
|
||||
|
||||
```sh
|
||||
pnpm faber
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
To set up a connection:
|
||||
|
||||
- Select 'receive connection invitation' in Alice and 'create connection invitation' in Faber
|
||||
- Faber will print a invitation link which you then copy and paste to Alice
|
||||
- You have now set up a connection!
|
||||
|
||||
To offer a credential:
|
||||
|
||||
- Select 'offer credential' in Faber
|
||||
- Faber will start with registering a schema and the credential definition accordingly
|
||||
- You have now send a credential offer to Alice!
|
||||
- Go to Alice to accept the incoming credential offer by selecting 'yes'.
|
||||
|
||||
To request a proof:
|
||||
|
||||
- Select 'request proof' in Faber
|
||||
- Faber will create a new proof attribute and will then send a proof request to Alice!
|
||||
- Go to Alice to accept the incoming proof request
|
||||
|
||||
To send a basic message:
|
||||
|
||||
- Select 'send message' in either one of the Agents
|
||||
- Type your message and press enter
|
||||
- Message sent!
|
||||
|
||||
Exit:
|
||||
|
||||
- Select 'exit' to shutdown the agent.
|
||||
|
||||
Restart:
|
||||
|
||||
- Select 'restart', to shutdown the current agent and start a new one
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "credo-demo",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/openwallet-foundation/credo-ts",
|
||||
"directory": "demo/"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"holder": "ts-node src/HolderInquirer.ts",
|
||||
"issuer": "ts-node src/IssuerInquirer.ts",
|
||||
"index": "ts-node src/Index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hyperledger/indy-vdr-nodejs": "^0.2.2",
|
||||
"@hyperledger/anoncreds-nodejs": "^0.2.2",
|
||||
"@openwallet-foundation/askar-nodejs": "^0.3.1",
|
||||
"inquirer": "^8.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@credo-ts/anoncreds": "workspace:*",
|
||||
"@credo-ts/askar": "workspace:*",
|
||||
"@credo-ts/core": "workspace:*",
|
||||
"@credo-ts/didcomm": "workspace:*",
|
||||
"@credo-ts/indy-vdr": "workspace:*",
|
||||
"@credo-ts/cheqd": "workspace:*",
|
||||
"@credo-ts/node": "workspace:*",
|
||||
"@types/figlet": "^1.5.4",
|
||||
"@types/inquirer": "^8.2.6",
|
||||
"clear": "^0.1.0",
|
||||
"figlet": "^1.5.2",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
import type { InitConfig } from '@credo-ts/core'
|
||||
import type { DidCommModuleConfigOptions } from '@credo-ts/didcomm'
|
||||
import type { IndyVdrPoolConfig } from '@credo-ts/indy-vdr'
|
||||
|
||||
import {
|
||||
AnonCredsCredentialFormatService,
|
||||
AnonCredsModule,
|
||||
AnonCredsProofFormatService,
|
||||
LegacyIndyCredentialFormatService,
|
||||
LegacyIndyProofFormatService,
|
||||
V1CredentialProtocol,
|
||||
V1ProofProtocol,
|
||||
} from '@credo-ts/anoncreds'
|
||||
import { AskarModule } from '@credo-ts/askar'
|
||||
import {
|
||||
CheqdAnonCredsRegistry,
|
||||
CheqdDidRegistrar,
|
||||
CheqdDidResolver,
|
||||
CheqdModule,
|
||||
CheqdModuleConfig,
|
||||
} from '@credo-ts/cheqd'
|
||||
import { DidsModule, Agent, ConsoleLogger, LogLevel } from '@credo-ts/core'
|
||||
import {
|
||||
ConnectionsModule,
|
||||
V2ProofProtocol,
|
||||
V2CredentialProtocol,
|
||||
ProofsModule,
|
||||
AutoAcceptProof,
|
||||
AutoAcceptCredential,
|
||||
CredentialsModule,
|
||||
HttpOutboundTransport,
|
||||
getDefaultDidcommModules,
|
||||
} from '@credo-ts/didcomm'
|
||||
import { IndyVdrIndyDidResolver, IndyVdrAnonCredsRegistry, IndyVdrModule } from '@credo-ts/indy-vdr'
|
||||
import { agentDependencies, HttpInboundTransport } from '@credo-ts/node'
|
||||
import { anoncreds } from '@hyperledger/anoncreds-nodejs'
|
||||
import { indyVdr } from '@hyperledger/indy-vdr-nodejs'
|
||||
import { askar } from '@openwallet-foundation/askar-nodejs'
|
||||
|
||||
import { greenText } from './OutputClass'
|
||||
|
||||
const bcovrin = `{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node1","blskey":"4N8aUNHSgjQVgkpm8nhNEfDf6txHznoYREg9kirmJrkivgL4oSEimFF6nsQ6M41QvhM2Z33nves5vfSn9n1UwNFJBYtWVnHYMATn76vLuL3zU88KyeAYcHfsih3He6UHcXDxcaecHVz6jhCYz1P2UZn2bDVruL5wXpehgBfBaLKm3Ba","blskey_pop":"RahHYiCvoNCtPTrVtP7nMC5eTYrsUA8WjXbdhNc8debh1agE9bGiJxWBXYNFbnJXoXhWFMvyqhqhRoq737YQemH5ik9oL7R4NTTCz2LEZhkgLJzB3QRQqJyBNyv7acbdHrAT8nQ9UkLbaVL9NBpnWXBTw4LEMePaSHEw66RzPNdAX1","client_ip":"10.133.133.1","client_port":9702,"node_ip":"10.133.133.1","node_port":9701,"services":["VALIDATOR"]},"dest":"Gw6pDLhcBcoQesN72qfotTgFa7cbuqZpkX3Xo6pLhPhv"},"metadata":{"from":"Th7MpTaRZVRYnPiabds81Y"},"type":"0"},"txnMetadata":{"seqNo":1,"txnId":"fea82e10e894419fe2bea7d96296a6d46f50f93f9eeda954ec461b2ed2950b62"},"ver":"1"}
|
||||
{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node2","blskey":"37rAPpXVoxzKhz7d9gkUe52XuXryuLXoM6P6LbWDB7LSbG62Lsb33sfG7zqS8TK1MXwuCHj1FKNzVpsnafmqLG1vXN88rt38mNFs9TENzm4QHdBzsvCuoBnPH7rpYYDo9DZNJePaDvRvqJKByCabubJz3XXKbEeshzpz4Ma5QYpJqjk","blskey_pop":"Qr658mWZ2YC8JXGXwMDQTzuZCWF7NK9EwxphGmcBvCh6ybUuLxbG65nsX4JvD4SPNtkJ2w9ug1yLTj6fgmuDg41TgECXjLCij3RMsV8CwewBVgVN67wsA45DFWvqvLtu4rjNnE9JbdFTc1Z4WCPA3Xan44K1HoHAq9EVeaRYs8zoF5","client_ip":"10.133.133.2","client_port":9704,"node_ip":"10.133.133.2","node_port":9703,"services":["VALIDATOR"]},"dest":"8ECVSk179mjsjKRLWiQtssMLgp6EPhWXtaYyStWPSGAb"},"metadata":{"from":"EbP4aYNeTHL6q385GuVpRV"},"type":"0"},"txnMetadata":{"seqNo":2,"txnId":"1ac8aece2a18ced660fef8694b61aac3af08ba875ce3026a160acbc3a3af35fc"},"ver":"1"}
|
||||
{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node3","blskey":"3WFpdbg7C5cnLYZwFZevJqhubkFALBfCBBok15GdrKMUhUjGsk3jV6QKj6MZgEubF7oqCafxNdkm7eswgA4sdKTRc82tLGzZBd6vNqU8dupzup6uYUf32KTHTPQbuUM8Yk4QFXjEf2Usu2TJcNkdgpyeUSX42u5LqdDDpNSWUK5deC5","blskey_pop":"QwDeb2CkNSx6r8QC8vGQK3GRv7Yndn84TGNijX8YXHPiagXajyfTjoR87rXUu4G4QLk2cF8NNyqWiYMus1623dELWwx57rLCFqGh7N4ZRbGDRP4fnVcaKg1BcUxQ866Ven4gw8y4N56S5HzxXNBZtLYmhGHvDtk6PFkFwCvxYrNYjh","client_ip":"10.133.133.3","client_port":9706,"node_ip":"10.133.133.3","node_port":9705,"services":["VALIDATOR"]},"dest":"DKVxG2fXXTU8yT5N7hGEbXB3dfdAnYv1JczDUHpmDxya"},"metadata":{"from":"4cU41vWW82ArfxJxHkzXPG"},"type":"0"},"txnMetadata":{"seqNo":3,"txnId":"7e9f355dffa78ed24668f0e0e369fd8c224076571c51e2ea8be5f26479edebe4"},"ver":"1"}
|
||||
{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node4","blskey":"2zN3bHM1m4rLz54MJHYSwvqzPchYp8jkHswveCLAEJVcX6Mm1wHQD1SkPYMzUDTZvWvhuE6VNAkK3KxVeEmsanSmvjVkReDeBEMxeDaayjcZjFGPydyey1qxBHmTvAnBKoPydvuTAqx5f7YNNRAdeLmUi99gERUU7TD8KfAa6MpQ9bw","blskey_pop":"RPLagxaR5xdimFzwmzYnz4ZhWtYQEj8iR5ZU53T2gitPCyCHQneUn2Huc4oeLd2B2HzkGnjAff4hWTJT6C7qHYB1Mv2wU5iHHGFWkhnTX9WsEAbunJCV2qcaXScKj4tTfvdDKfLiVuU2av6hbsMztirRze7LvYBkRHV3tGwyCptsrP","client_ip":"10.133.133.4","client_port":9708,"node_ip":"10.133.133.4","node_port":9707,"services":["VALIDATOR"]},"dest":"4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA"},"metadata":{"from":"TWwCRQRZ2ZHMJFn9TzLp7W"},"type":"0"},"txnMetadata":{"seqNo":4,"txnId":"aa5e817d7cc626170eca175822029339a444eb0ee8f0bd20d3b0b76e566fb008"},"ver":"1"}`
|
||||
|
||||
export const indyNetworkConfig = {
|
||||
genesisTransactions: bcovrin,
|
||||
indyNamespace: 'bcovrin:test',
|
||||
isProduction: false,
|
||||
connectOnStartup: true,
|
||||
} satisfies IndyVdrPoolConfig
|
||||
|
||||
type DemoAgent = Agent<ReturnType<typeof getAskarAnonCredsIndyModules>>
|
||||
|
||||
export class BaseAgent {
|
||||
public port: number
|
||||
public name: string
|
||||
public config: InitConfig
|
||||
public agent: DemoAgent
|
||||
|
||||
public constructor({ port, name }: { port: number; name: string }) {
|
||||
this.name = name
|
||||
this.port = port
|
||||
|
||||
const config = {
|
||||
label: name,
|
||||
walletConfig: {
|
||||
id: name,
|
||||
key: name,
|
||||
},
|
||||
//logger: new ConsoleLogger(LogLevel.debug),
|
||||
} satisfies InitConfig
|
||||
|
||||
this.config = config
|
||||
|
||||
this.agent = new Agent({
|
||||
config,
|
||||
dependencies: agentDependencies,
|
||||
modules: getAskarAnonCredsIndyModules({ endpoints: [`http://localhost:${this.port}`] }),
|
||||
})
|
||||
this.agent.modules.didcomm.registerInboundTransport(new HttpInboundTransport({ port }))
|
||||
this.agent.modules.didcomm.registerOutboundTransport(new HttpOutboundTransport())
|
||||
}
|
||||
|
||||
public async initializeAgent() {
|
||||
await this.agent.initialize()
|
||||
|
||||
console.log(greenText(`\nAgent ${this.name} created!\n`))
|
||||
}
|
||||
}
|
||||
|
||||
function getAskarAnonCredsIndyModules(didcommConfig: DidCommModuleConfigOptions) {
|
||||
const legacyIndyCredentialFormatService = new LegacyIndyCredentialFormatService()
|
||||
const legacyIndyProofFormatService = new LegacyIndyProofFormatService()
|
||||
|
||||
return {
|
||||
...getDefaultDidcommModules(didcommConfig),
|
||||
connections: new ConnectionsModule({
|
||||
autoAcceptConnections: true,
|
||||
}),
|
||||
credentials: new CredentialsModule({
|
||||
autoAcceptCredentials: AutoAcceptCredential.ContentApproved,
|
||||
credentialProtocols: [
|
||||
new V1CredentialProtocol({
|
||||
indyCredentialFormat: legacyIndyCredentialFormatService,
|
||||
}),
|
||||
new V2CredentialProtocol({
|
||||
credentialFormats: [legacyIndyCredentialFormatService, new AnonCredsCredentialFormatService()],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
proofs: new ProofsModule({
|
||||
autoAcceptProofs: AutoAcceptProof.ContentApproved,
|
||||
proofProtocols: [
|
||||
new V1ProofProtocol({
|
||||
indyProofFormat: legacyIndyProofFormatService,
|
||||
}),
|
||||
new V2ProofProtocol({
|
||||
proofFormats: [legacyIndyProofFormatService, new AnonCredsProofFormatService()],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
anoncreds: new AnonCredsModule({
|
||||
registries: [new IndyVdrAnonCredsRegistry(), new CheqdAnonCredsRegistry()],
|
||||
anoncreds,
|
||||
}),
|
||||
indyVdr: new IndyVdrModule({
|
||||
indyVdr,
|
||||
networks: [indyNetworkConfig],
|
||||
}),
|
||||
cheqd: new CheqdModule(
|
||||
new CheqdModuleConfig({
|
||||
networks: [
|
||||
{
|
||||
network: 'testnet',
|
||||
cosmosPayerSeed:
|
||||
'robust across amount corn curve panther opera wish toe ring bleak empower wreck party abstract glad average muffin picnic jar squeeze annual long aunt',
|
||||
},
|
||||
],
|
||||
})
|
||||
),
|
||||
dids: new DidsModule({
|
||||
resolvers: [new IndyVdrIndyDidResolver(), new CheqdDidResolver()],
|
||||
registrars: [new CheqdDidRegistrar()],
|
||||
}),
|
||||
askar: new AskarModule({
|
||||
askar,
|
||||
}),
|
||||
} as const
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import { prompt } from 'inquirer'
|
||||
|
||||
import { Title } from './OutputClass'
|
||||
|
||||
export enum ConfirmOptions {
|
||||
Yes = 'yes',
|
||||
No = 'no',
|
||||
}
|
||||
|
||||
export class BaseInquirer {
|
||||
public optionsInquirer: { type: string; prefix: string; name: string; message: string; choices: string[] }
|
||||
public inputInquirer: { type: string; prefix: string; name: string; message: string; choices: string[] }
|
||||
|
||||
public constructor() {
|
||||
this.optionsInquirer = {
|
||||
type: 'list',
|
||||
prefix: '',
|
||||
name: 'options',
|
||||
message: '',
|
||||
choices: [],
|
||||
}
|
||||
|
||||
this.inputInquirer = {
|
||||
type: 'input',
|
||||
prefix: '',
|
||||
name: 'input',
|
||||
message: '',
|
||||
choices: [],
|
||||
}
|
||||
}
|
||||
|
||||
public inquireOptions(promptOptions: string[]) {
|
||||
this.optionsInquirer.message = Title.OptionsTitle
|
||||
this.optionsInquirer.choices = promptOptions
|
||||
return this.optionsInquirer
|
||||
}
|
||||
|
||||
public inquireInput(title: string) {
|
||||
this.inputInquirer.message = title
|
||||
return this.inputInquirer
|
||||
}
|
||||
|
||||
public inquireConfirmation(title: string) {
|
||||
this.optionsInquirer.message = title
|
||||
this.optionsInquirer.choices = [ConfirmOptions.Yes, ConfirmOptions.No]
|
||||
return this.optionsInquirer
|
||||
}
|
||||
|
||||
public async inquireMessage() {
|
||||
this.inputInquirer.message = Title.MessageTitle
|
||||
const message = await prompt([this.inputInquirer])
|
||||
|
||||
return message.input[0] === 'q' ? null : message.input
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
import type { ConnectionRecord, CredentialExchangeRecord, ProofExchangeRecord } from '@credo-ts/didcomm'
|
||||
|
||||
import { BaseAgent } from './BaseAgent'
|
||||
import { greenText, Output, redText } from './OutputClass'
|
||||
|
||||
export class Holder extends BaseAgent {
|
||||
public connected: boolean
|
||||
public connectionRecordFaberId?: string
|
||||
|
||||
public constructor(port: number, name: string) {
|
||||
super({ port, name })
|
||||
this.connected = false
|
||||
}
|
||||
|
||||
public static async build(): Promise<Holder> {
|
||||
const holder = new Holder(9004, 'holder')
|
||||
await holder.initializeAgent()
|
||||
return holder
|
||||
}
|
||||
|
||||
private async getConnectionRecord() {
|
||||
if (!this.connectionRecordFaberId) {
|
||||
throw Error(redText(Output.MissingConnectionRecord))
|
||||
}
|
||||
return await this.agent.modules.connections.getById(this.connectionRecordFaberId)
|
||||
}
|
||||
|
||||
private async receiveConnectionRequest(invitationUrl: string) {
|
||||
const { connectionRecord } = await this.agent.modules.oob.receiveInvitationFromUrl(invitationUrl)
|
||||
if (!connectionRecord) {
|
||||
throw new Error(redText(Output.NoConnectionRecordFromOutOfBand))
|
||||
}
|
||||
return connectionRecord
|
||||
}
|
||||
|
||||
private async waitForConnection(connectionRecord: ConnectionRecord) {
|
||||
connectionRecord = await this.agent.modules.connections.returnWhenIsConnected(connectionRecord.id)
|
||||
this.connected = true
|
||||
console.log("[HOLDER:]" + greenText(Output.ConnectionEstablished))
|
||||
return connectionRecord.id
|
||||
}
|
||||
|
||||
public async acceptConnection(invitation_url: string) {
|
||||
const connectionRecord = await this.receiveConnectionRequest(invitation_url)
|
||||
this.connectionRecordFaberId = await this.waitForConnection(connectionRecord)
|
||||
}
|
||||
|
||||
public async acceptCredentialOffer(credentialRecord: CredentialExchangeRecord) {
|
||||
await this.agent.modules.credentials.acceptOffer({
|
||||
credentialRecordId: credentialRecord.id,
|
||||
})
|
||||
|
||||
let [ credentials ] = await this.agent.modules.credentials.getAll();
|
||||
console.log(credentials);
|
||||
}
|
||||
|
||||
public async acceptProofRequest(proofRecord: ProofExchangeRecord) {
|
||||
const requestedCredentials = await this.agent.modules.proofs.selectCredentialsForRequest({
|
||||
proofRecordId: proofRecord.id,
|
||||
})
|
||||
|
||||
await this.agent.modules.proofs.acceptRequest({
|
||||
proofRecordId: proofRecord.id,
|
||||
proofFormats: requestedCredentials.proofFormats,
|
||||
})
|
||||
console.log(greenText('\nProof request accepted!\n'))
|
||||
}
|
||||
|
||||
public async sendMessage(message: string) {
|
||||
const connectionRecord = await this.getConnectionRecord()
|
||||
await this.agent.modules.basicMessages.sendMessage(connectionRecord.id, message)
|
||||
}
|
||||
|
||||
public async exit() {
|
||||
console.log(Output.Exit)
|
||||
await this.agent.shutdown()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
public async restart() {
|
||||
await this.agent.shutdown()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
import type { CredentialExchangeRecord, ProofExchangeRecord } from '@credo-ts/didcomm'
|
||||
|
||||
import { clear } from 'console'
|
||||
import { textSync } from 'figlet'
|
||||
import { prompt } from 'inquirer'
|
||||
|
||||
import { Holder } from './Holder'
|
||||
import { BaseInquirer, ConfirmOptions } from './BaseInquirer'
|
||||
import { Listener } from './Listener'
|
||||
import { Title } from './OutputClass'
|
||||
|
||||
export const runHolder = async () => {
|
||||
clear()
|
||||
console.log(textSync('Holder', { horizontalLayout: 'full' }))
|
||||
const holder = await HolderInquirer.build()
|
||||
await holder.processAnswer()
|
||||
}
|
||||
|
||||
enum PromptOptions {
|
||||
ReceiveConnectionUrl = 'Receive connection invitation',
|
||||
SendMessage = 'Send message',
|
||||
Exit = 'Exit',
|
||||
Restart = 'Restart',
|
||||
}
|
||||
|
||||
export class HolderInquirer extends BaseInquirer {
|
||||
public holder: Holder
|
||||
public promptOptionsString: string[]
|
||||
public listener: Listener
|
||||
|
||||
public constructor(holder: Holder) {
|
||||
super()
|
||||
this.holder = holder
|
||||
this.listener = new Listener()
|
||||
this.promptOptionsString = Object.values(PromptOptions)
|
||||
this.listener.messageListener(this.holder.agent, this.holder.name)
|
||||
}
|
||||
|
||||
public static async build(): Promise<HolderInquirer> {
|
||||
const holder = await Holder.build()
|
||||
return new HolderInquirer(holder)
|
||||
}
|
||||
|
||||
private async getPromptChoice() {
|
||||
if (this.holder.connectionRecordFaberId) return prompt([this.inquireOptions(this.promptOptionsString)])
|
||||
|
||||
const reducedOption = [PromptOptions.ReceiveConnectionUrl, PromptOptions.Exit, PromptOptions.Restart]
|
||||
return prompt([this.inquireOptions(reducedOption)])
|
||||
}
|
||||
|
||||
public async processAnswer() {
|
||||
const choice = await this.getPromptChoice()
|
||||
if (this.listener.on) return
|
||||
|
||||
switch (choice.options) {
|
||||
case PromptOptions.ReceiveConnectionUrl:
|
||||
await this.connection()
|
||||
break
|
||||
case PromptOptions.SendMessage:
|
||||
await this.message()
|
||||
break
|
||||
case PromptOptions.Exit:
|
||||
await this.exit()
|
||||
break
|
||||
case PromptOptions.Restart:
|
||||
await this.restart()
|
||||
return
|
||||
}
|
||||
await this.processAnswer()
|
||||
}
|
||||
|
||||
public async acceptCredentialOffer(credentialRecord: CredentialExchangeRecord) {
|
||||
const confirm = await prompt([this.inquireConfirmation(Title.CredentialOfferTitle)])
|
||||
if (confirm.options === ConfirmOptions.No) {
|
||||
await this.holder.agent.modules.credentials.declineOffer(credentialRecord.id)
|
||||
} else if (confirm.options === ConfirmOptions.Yes) {
|
||||
await this.holder.acceptCredentialOffer(credentialRecord)
|
||||
}
|
||||
}
|
||||
|
||||
public async acceptProofRequest(proofRecord: ProofExchangeRecord) {
|
||||
const confirm = await prompt([this.inquireConfirmation(Title.ProofRequestTitle)])
|
||||
if (confirm.options === ConfirmOptions.No) {
|
||||
await this.holder.agent.modules.proofs.declineRequest({ proofRecordId: proofRecord.id })
|
||||
} else if (confirm.options === ConfirmOptions.Yes) {
|
||||
await this.holder.acceptProofRequest(proofRecord)
|
||||
}
|
||||
}
|
||||
|
||||
public async connection() {
|
||||
const title = Title.InvitationTitle
|
||||
const getUrl = await prompt([this.inquireInput(title)])
|
||||
await this.holder.acceptConnection(getUrl.input)
|
||||
if (!this.holder.connected) return
|
||||
|
||||
this.listener.credentialOfferListener(this.holder, this)
|
||||
this.listener.proofRequestListener(this.holder, this)
|
||||
}
|
||||
|
||||
public async message() {
|
||||
const message = await this.inquireMessage()
|
||||
if (!message) return
|
||||
|
||||
await this.holder.sendMessage(message)
|
||||
}
|
||||
|
||||
public async exit() {
|
||||
const confirm = await prompt([this.inquireConfirmation(Title.ConfirmTitle)])
|
||||
if (confirm.options === ConfirmOptions.No) {
|
||||
return
|
||||
} else if (confirm.options === ConfirmOptions.Yes) {
|
||||
await this.holder.exit()
|
||||
}
|
||||
}
|
||||
|
||||
public async restart() {
|
||||
const confirm = await prompt([this.inquireConfirmation(Title.ConfirmTitle)])
|
||||
if (confirm.options === ConfirmOptions.No) {
|
||||
await this.processAnswer()
|
||||
return
|
||||
} else if (confirm.options === ConfirmOptions.Yes) {
|
||||
await this.holder.restart()
|
||||
await runHolder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void runHolder()
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
import { Issuer, RegistryOptions } from './Issuer'
|
||||
import { Holder } from './Holder'
|
||||
import { Listener } from './Listener'
|
||||
import { CredentialExchangeRecord } from '@credo-ts/didcomm'
|
||||
|
||||
function delay(ms: number) {
|
||||
return new Promise( resolve => setTimeout(resolve, ms) );
|
||||
}
|
||||
|
||||
export const runIndex = async () => {
|
||||
const index = await IndexThing.build()
|
||||
index.process()
|
||||
}
|
||||
|
||||
export class IndexThing {
|
||||
public issuer: Issuer;
|
||||
public holder: Holder;
|
||||
public listener: Listener;
|
||||
|
||||
public constructor(issuer: Issuer, holder: Holder)
|
||||
{
|
||||
this.issuer = issuer;
|
||||
this.holder = holder;
|
||||
this.listener = new Listener()
|
||||
}
|
||||
|
||||
public static async build(): Promise<IndexThing>
|
||||
{
|
||||
const issuer = await Issuer.build()
|
||||
const holder = await Holder.build()
|
||||
return new IndexThing(issuer, holder)
|
||||
}
|
||||
|
||||
|
||||
public async process()
|
||||
{
|
||||
let url = await this.issuer.createConnectionInvite()
|
||||
console.log(url);
|
||||
|
||||
[await this.holderConnection(url), await this.issuer.waitForConnection()];
|
||||
//this.issuer.waitForConnection()
|
||||
//this.holderConnection(url)
|
||||
|
||||
await this.issuer.importDid(RegistryOptions.indy)
|
||||
await this.credential()
|
||||
|
||||
}
|
||||
|
||||
public async issuerWaitConnection()
|
||||
{
|
||||
await this.issuer.waitForConnection()
|
||||
}
|
||||
|
||||
public async credential()
|
||||
{
|
||||
await this.issuer.importDid(RegistryOptions.indy)
|
||||
await this.issuer.issueCredential()
|
||||
}
|
||||
|
||||
public async acceptCredentialOffer(credentialRecord: CredentialExchangeRecord)
|
||||
{
|
||||
await delay(10000);
|
||||
await this.holder.acceptCredentialOffer(credentialRecord)
|
||||
}
|
||||
|
||||
public async holderConnection(url: string)
|
||||
{
|
||||
await delay(10000);
|
||||
await this.holder.acceptConnection(url);
|
||||
if (!this.holder.connected) return
|
||||
|
||||
this.listener.credentialOfferListener(this.holder, this)
|
||||
}
|
||||
}
|
||||
|
||||
runIndex()
|
||||
|
|
@ -0,0 +1,297 @@
|
|||
import type { RegisterCredentialDefinitionReturnStateFinished } from '@credo-ts/anoncreds'
|
||||
import type { ConnectionRecord, ConnectionStateChangedEvent } from '@credo-ts/didcomm'
|
||||
import type { IndyVdrRegisterSchemaOptions, IndyVdrRegisterCredentialDefinitionOptions } from '@credo-ts/indy-vdr'
|
||||
import type BottomBar from 'inquirer/lib/ui/bottom-bar'
|
||||
|
||||
import { KeyType, TypedArrayEncoder, utils } from '@credo-ts/core'
|
||||
import { ConnectionEventTypes } from '@credo-ts/didcomm'
|
||||
import { ui } from 'inquirer'
|
||||
|
||||
import { BaseAgent, indyNetworkConfig } from './BaseAgent'
|
||||
import { Color, Output, greenText, purpleText, redText } from './OutputClass'
|
||||
|
||||
export enum RegistryOptions {
|
||||
indy = 'did:indy',
|
||||
cheqd = 'did:cheqd',
|
||||
}
|
||||
|
||||
export class Issuer extends BaseAgent {
|
||||
public outOfBandId?: string
|
||||
public credentialDefinition?: RegisterCredentialDefinitionReturnStateFinished
|
||||
public anonCredsIssuerId?: string
|
||||
public ui: BottomBar
|
||||
|
||||
public constructor(port: number, name: string) {
|
||||
super({ port, name })
|
||||
this.ui = new ui.BottomBar()
|
||||
}
|
||||
|
||||
public static async build(): Promise<Issuer> {
|
||||
const issuer = new Issuer(9005, 'issuer')
|
||||
await issuer.initializeAgent()
|
||||
return issuer
|
||||
}
|
||||
|
||||
public async importDid(registry: string) {
|
||||
// NOTE: we assume the did is already registered on the ledger, we just store the private key in the wallet
|
||||
// and store the existing did in the wallet
|
||||
// indy did is based on private key (seed)
|
||||
const unqualifiedIndyDid = '2jEvRuKmfBJTRa7QowDpNN'
|
||||
const cheqdDid = 'did:cheqd:testnet:d37eba59-513d-42d3-8f9f-d1df0548b675'
|
||||
const indyDid = `did:indy:${indyNetworkConfig.indyNamespace}:${unqualifiedIndyDid}`
|
||||
|
||||
const did = registry === RegistryOptions.indy ? indyDid : cheqdDid
|
||||
await this.agent.dids.import({
|
||||
did,
|
||||
overwrite: true,
|
||||
privateKeys: [
|
||||
{
|
||||
keyType: KeyType.Ed25519,
|
||||
privateKey: TypedArrayEncoder.fromString('afjdemoverysercure00000000000000'),
|
||||
},
|
||||
],
|
||||
})
|
||||
this.anonCredsIssuerId = did
|
||||
}
|
||||
|
||||
private async getConnectionRecord() {
|
||||
if (!this.outOfBandId) {
|
||||
throw Error(redText(Output.MissingConnectionRecord))
|
||||
}
|
||||
|
||||
const [connection] = await this.agent.modules.connections.findAllByOutOfBandId(this.outOfBandId)
|
||||
|
||||
if (!connection) {
|
||||
throw Error(redText(Output.MissingConnectionRecord))
|
||||
}
|
||||
|
||||
return connection
|
||||
}
|
||||
|
||||
private async printConnectionInvite() {
|
||||
const outOfBand = await this.agent.modules.oob.createInvitation()
|
||||
this.outOfBandId = outOfBand.id
|
||||
|
||||
console.log(
|
||||
Output.ConnectionLink,
|
||||
outOfBand.outOfBandInvitation.toUrl({ domain: `http://localhost:${this.port}` }),
|
||||
'\n'
|
||||
)
|
||||
}
|
||||
|
||||
public async createConnectionInvite() {
|
||||
const outOfBand = await this.agent.modules.oob.createInvitation()
|
||||
this.outOfBandId = outOfBand.id
|
||||
|
||||
return outOfBand.outOfBandInvitation.toUrl({ domain: `http://localhost:${this.port}` })
|
||||
}
|
||||
|
||||
public async waitForConnection() {
|
||||
if (!this.outOfBandId) {
|
||||
throw new Error(redText(Output.MissingConnectionRecord))
|
||||
}
|
||||
|
||||
console.log('Waiting for Alice to finish connection...')
|
||||
|
||||
const getConnectionRecord = (outOfBandId: string) =>
|
||||
new Promise<ConnectionRecord>((resolve, reject) => {
|
||||
// Timeout of 20 seconds
|
||||
const timeoutId = setTimeout(() => reject(new Error(redText(Output.MissingConnectionRecord))), 20000)
|
||||
console.log("son ofa bitch")
|
||||
|
||||
// Start listener
|
||||
this.agent.events.on<ConnectionStateChangedEvent>(ConnectionEventTypes.ConnectionStateChanged, (e) => {
|
||||
if (e.payload.connectionRecord.outOfBandId !== outOfBandId) return
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
resolve(e.payload.connectionRecord)
|
||||
})
|
||||
|
||||
// Also retrieve the connection record by invitation if the event has already fired
|
||||
void this.agent.modules.connections.findAllByOutOfBandId(outOfBandId).then(([connectionRecord]) => {
|
||||
if (connectionRecord) {
|
||||
clearTimeout(timeoutId)
|
||||
resolve(connectionRecord)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const connectionRecord = await getConnectionRecord(this.outOfBandId)
|
||||
|
||||
try {
|
||||
await this.agent.modules.connections.returnWhenIsConnected(connectionRecord.id)
|
||||
} catch (e) {
|
||||
console.log(redText(`\nTimeout of 20 seconds reached.. Returning to home screen.\n`))
|
||||
return
|
||||
}
|
||||
console.log("deez")
|
||||
console.log(greenText(Output.ConnectionEstablished))
|
||||
}
|
||||
|
||||
public async setupConnection() {
|
||||
// await this.printConnectionInvite()
|
||||
await this.waitForConnection()
|
||||
}
|
||||
|
||||
private printSchema(name: string, version: string, attributes: string[]) {
|
||||
console.log(`\n\ndidcommThe credential definition will look like this:\n`)
|
||||
console.log(purpleText(`Name: ${Color.Reset}${name}`))
|
||||
console.log(purpleText(`Version: ${Color.Reset}${version}`))
|
||||
console.log(purpleText(`Attributes: ${Color.Reset}${attributes[0]}, ${attributes[1]}, ${attributes[2]}\n`))
|
||||
}
|
||||
|
||||
private async registerSchema() {
|
||||
if (!this.anonCredsIssuerId) {
|
||||
throw new Error(redText('Missing anoncreds issuerId'))
|
||||
}
|
||||
const schemaTemplate = {
|
||||
name: 'Issuer College' + utils.uuid(),
|
||||
version: '1.0.0',
|
||||
attrNames: ['name', 'degree', 'date'],
|
||||
issuerId: this.anonCredsIssuerId,
|
||||
}
|
||||
this.printSchema(schemaTemplate.name, schemaTemplate.version, schemaTemplate.attrNames)
|
||||
this.ui.updateBottomBar(greenText('\nRegistering schema...\n', false))
|
||||
|
||||
const { schemaState } = await this.agent.modules.anoncreds.registerSchema<IndyVdrRegisterSchemaOptions>({
|
||||
schema: schemaTemplate,
|
||||
options: {
|
||||
endorserMode: 'internal',
|
||||
endorserDid: this.anonCredsIssuerId,
|
||||
},
|
||||
})
|
||||
|
||||
if (schemaState.state !== 'finished') {
|
||||
throw new Error(
|
||||
`Error registering schema: ${schemaState.state === 'failed' ? schemaState.reason : 'Not Finished'}`
|
||||
)
|
||||
}
|
||||
this.ui.updateBottomBar('\nSchema registered!\n')
|
||||
return schemaState
|
||||
}
|
||||
|
||||
private async registerCredentialDefinition(schemaId: string) {
|
||||
if (!this.anonCredsIssuerId) {
|
||||
throw new Error(redText('Missing anoncreds issuerId'))
|
||||
}
|
||||
|
||||
this.ui.updateBottomBar('\nRegistering credential definition...\n')
|
||||
const { credentialDefinitionState } =
|
||||
await this.agent.modules.anoncreds.registerCredentialDefinition<IndyVdrRegisterCredentialDefinitionOptions>({
|
||||
credentialDefinition: {
|
||||
schemaId,
|
||||
issuerId: this.anonCredsIssuerId,
|
||||
tag: 'latest',
|
||||
},
|
||||
options: {
|
||||
supportRevocation: false,
|
||||
endorserMode: 'internal',
|
||||
endorserDid: this.anonCredsIssuerId,
|
||||
},
|
||||
})
|
||||
|
||||
if (credentialDefinitionState.state !== 'finished') {
|
||||
throw new Error(
|
||||
`Error registering credential definition: ${
|
||||
credentialDefinitionState.state === 'failed' ? credentialDefinitionState.reason : 'Not Finished'
|
||||
}}`
|
||||
)
|
||||
}
|
||||
|
||||
this.credentialDefinition = credentialDefinitionState
|
||||
this.ui.updateBottomBar('\nCredential definition registered!!\n')
|
||||
return this.credentialDefinition
|
||||
}
|
||||
|
||||
public async issueCredential() {
|
||||
const schema = await this.registerSchema()
|
||||
const credentialDefinition = await this.registerCredentialDefinition(schema.schemaId)
|
||||
const connectionRecord = await this.getConnectionRecord()
|
||||
|
||||
this.ui.updateBottomBar('\nSending credential offer...\n')
|
||||
|
||||
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.ui.updateBottomBar(
|
||||
`\nCredential offer sent!\n\nGo to the Alice agent to accept the credential offer\n\n${Color.Reset}`
|
||||
)
|
||||
}
|
||||
|
||||
private async printProofFlow(print: string) {
|
||||
this.ui.updateBottomBar(print)
|
||||
await new Promise((f) => setTimeout(f, 2000))
|
||||
}
|
||||
|
||||
private async newProofAttribute() {
|
||||
await this.printProofFlow(greenText(`Creating new proof attribute for 'name' ...\n`))
|
||||
const proofAttribute = {
|
||||
name: {
|
||||
name: 'name',
|
||||
restrictions: [
|
||||
{
|
||||
cred_def_id: this.credentialDefinition?.credentialDefinitionId,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
return proofAttribute
|
||||
}
|
||||
|
||||
public async sendProofRequest() {
|
||||
const connectionRecord = await this.getConnectionRecord()
|
||||
const proofAttribute = await this.newProofAttribute()
|
||||
await this.printProofFlow(greenText('\nRequesting proof...\n', false))
|
||||
|
||||
await this.agent.modules.proofs.requestProof({
|
||||
protocolVersion: 'v2',
|
||||
connectionId: connectionRecord.id,
|
||||
proofFormats: {
|
||||
anoncreds: {
|
||||
name: 'proof-request',
|
||||
version: '1.0',
|
||||
requested_attributes: proofAttribute,
|
||||
},
|
||||
},
|
||||
})
|
||||
this.ui.updateBottomBar(
|
||||
`\nProof request sent!\n\nGo to the Alice agent to accept the proof request\n\n${Color.Reset}`
|
||||
)
|
||||
}
|
||||
|
||||
public async sendMessage(message: string) {
|
||||
const connectionRecord = await this.getConnectionRecord()
|
||||
await this.agent.modules.basicMessages.sendMessage(connectionRecord.id, message)
|
||||
}
|
||||
|
||||
public async exit() {
|
||||
console.log(Output.Exit)
|
||||
await this.agent.shutdown()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
public async restart() {
|
||||
await this.agent.shutdown()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
import { clear } from 'console'
|
||||
import { textSync } from 'figlet'
|
||||
import { prompt } from 'inquirer'
|
||||
|
||||
import { BaseInquirer, ConfirmOptions } from './BaseInquirer'
|
||||
import { Issuer, RegistryOptions } from './Issuer'
|
||||
import { Listener } from './Listener'
|
||||
import { Title } from './OutputClass'
|
||||
|
||||
export const runIssuer = async () => {
|
||||
clear()
|
||||
console.log(textSync('Issuer', { horizontalLayout: 'full' }))
|
||||
const issuer = await IssuerInquirer.build()
|
||||
await issuer.processAnswer()
|
||||
}
|
||||
|
||||
enum PromptOptions {
|
||||
CreateConnection = 'Create connection invitation',
|
||||
OfferCredential = 'Offer credential',
|
||||
RequestProof = 'Request proof',
|
||||
SendMessage = 'Send message',
|
||||
Exit = 'Exit',
|
||||
Restart = 'Restart',
|
||||
}
|
||||
|
||||
export class IssuerInquirer extends BaseInquirer {
|
||||
public issuer: Issuer
|
||||
public promptOptionsString: string[]
|
||||
public listener: Listener
|
||||
|
||||
public constructor(issuer: Issuer) {
|
||||
super()
|
||||
this.issuer = issuer
|
||||
this.listener = new Listener()
|
||||
this.promptOptionsString = Object.values(PromptOptions)
|
||||
this.listener.messageListener(this.issuer.agent, this.issuer.name)
|
||||
}
|
||||
|
||||
public static async build(): Promise<IssuerInquirer> {
|
||||
const issuer = await Issuer.build()
|
||||
return new IssuerInquirer(issuer)
|
||||
}
|
||||
|
||||
private async getPromptChoice() {
|
||||
if (this.issuer.outOfBandId) return prompt([this.inquireOptions(this.promptOptionsString)])
|
||||
|
||||
const reducedOption = [PromptOptions.CreateConnection, PromptOptions.Exit, PromptOptions.Restart]
|
||||
return prompt([this.inquireOptions(reducedOption)])
|
||||
}
|
||||
|
||||
public async processAnswer() {
|
||||
const choice = await this.getPromptChoice()
|
||||
if (this.listener.on) return
|
||||
|
||||
switch (choice.options) {
|
||||
case PromptOptions.CreateConnection:
|
||||
await this.connection()
|
||||
break
|
||||
case PromptOptions.OfferCredential:
|
||||
await this.credential()
|
||||
return
|
||||
case PromptOptions.RequestProof:
|
||||
await this.proof()
|
||||
return
|
||||
case PromptOptions.SendMessage:
|
||||
await this.message()
|
||||
break
|
||||
case PromptOptions.Exit:
|
||||
await this.exit()
|
||||
break
|
||||
case PromptOptions.Restart:
|
||||
await this.restart()
|
||||
return
|
||||
}
|
||||
await this.processAnswer()
|
||||
}
|
||||
|
||||
public async connection() {
|
||||
await this.issuer.setupConnection()
|
||||
}
|
||||
|
||||
public async exitUseCase(title: string) {
|
||||
const confirm = await prompt([this.inquireConfirmation(title)])
|
||||
if (confirm.options === ConfirmOptions.No) {
|
||||
return false
|
||||
} else if (confirm.options === ConfirmOptions.Yes) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public async credential() {
|
||||
const registry = await prompt([this.inquireOptions([RegistryOptions.indy, RegistryOptions.cheqd])])
|
||||
await this.issuer.importDid(registry.options)
|
||||
await this.issuer.issueCredential()
|
||||
const title = 'Is the credential offer accepted?'
|
||||
await this.listener.newAcceptedPrompt(title, this)
|
||||
}
|
||||
|
||||
public async proof() {
|
||||
await this.issuer.sendProofRequest()
|
||||
const title = 'Is the proof request accepted?'
|
||||
await this.listener.newAcceptedPrompt(title, this)
|
||||
}
|
||||
|
||||
public async message() {
|
||||
const message = await this.inquireMessage()
|
||||
if (!message) return
|
||||
|
||||
await this.issuer.sendMessage(message)
|
||||
}
|
||||
|
||||
public async exit() {
|
||||
const confirm = await prompt([this.inquireConfirmation(Title.ConfirmTitle)])
|
||||
if (confirm.options === ConfirmOptions.No) {
|
||||
return
|
||||
} else if (confirm.options === ConfirmOptions.Yes) {
|
||||
await this.issuer.exit()
|
||||
}
|
||||
}
|
||||
|
||||
public async restart() {
|
||||
const confirm = await prompt([this.inquireConfirmation(Title.ConfirmTitle)])
|
||||
if (confirm.options === ConfirmOptions.No) {
|
||||
await this.processAnswer()
|
||||
return
|
||||
} else if (confirm.options === ConfirmOptions.Yes) {
|
||||
await this.issuer.restart()
|
||||
await runIssuer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void runIssuer()
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
import type { Holder } from './Holder'
|
||||
import type { HolderInquirer } from './HolderInquirer'
|
||||
import type { Issuer } from './Issuer'
|
||||
import type { IssuerInquirer } from './IssuerInquirer'
|
||||
import type { Agent } from '@credo-ts/core'
|
||||
import type {
|
||||
BasicMessageStateChangedEvent,
|
||||
CredentialExchangeRecord,
|
||||
CredentialStateChangedEvent,
|
||||
ProofExchangeRecord,
|
||||
ProofStateChangedEvent,
|
||||
} from '@credo-ts/didcomm'
|
||||
import type BottomBar from 'inquirer/lib/ui/bottom-bar'
|
||||
|
||||
import {
|
||||
BasicMessageEventTypes,
|
||||
BasicMessageRole,
|
||||
CredentialEventTypes,
|
||||
CredentialState,
|
||||
ProofEventTypes,
|
||||
ProofState,
|
||||
} from '@credo-ts/didcomm'
|
||||
import { ui } from 'inquirer'
|
||||
|
||||
import { Color, purpleText } from './OutputClass'
|
||||
|
||||
export class Listener {
|
||||
public on: boolean
|
||||
private ui: BottomBar
|
||||
|
||||
public constructor() {
|
||||
this.on = false
|
||||
this.ui = new ui.BottomBar()
|
||||
}
|
||||
|
||||
private turnListenerOn() {
|
||||
this.on = true
|
||||
}
|
||||
|
||||
private turnListenerOff() {
|
||||
this.on = false
|
||||
}
|
||||
|
||||
private printCredentialAttributes(credentialRecord: CredentialExchangeRecord) {
|
||||
if (credentialRecord.credentialAttributes) {
|
||||
const attribute = credentialRecord.credentialAttributes
|
||||
console.log('\n\nCredential preview:')
|
||||
attribute.forEach((element) => {
|
||||
console.log(purpleText(`${element.name} ${Color.Reset}${element.value}`))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private async newCredentialPrompt(credentialRecord: CredentialExchangeRecord, aliceInquirer: HolderInquirer) {
|
||||
this.printCredentialAttributes(credentialRecord)
|
||||
this.turnListenerOn()
|
||||
await aliceInquirer.acceptCredentialOffer(credentialRecord)
|
||||
this.turnListenerOff()
|
||||
await aliceInquirer.processAnswer()
|
||||
}
|
||||
|
||||
public credentialOfferListener(alice: Holder, aliceInquirer: any) {
|
||||
alice.agent.events.on(
|
||||
CredentialEventTypes.CredentialStateChanged,
|
||||
async ({ payload }: CredentialStateChangedEvent) => {
|
||||
if (payload.credentialRecord.state === CredentialState.OfferReceived) {
|
||||
await this.newCredentialPrompt(payload.credentialRecord, aliceInquirer)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
public messageListener(agent: Agent, name: string) {
|
||||
agent.events.on(BasicMessageEventTypes.BasicMessageStateChanged, async (event: BasicMessageStateChangedEvent) => {
|
||||
if (event.payload.basicMessageRecord.role === BasicMessageRole.Receiver) {
|
||||
this.ui.updateBottomBar(purpleText(`\n${name} received a message: ${event.payload.message.content}\n`))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private async newProofRequestPrompt(proofRecord: ProofExchangeRecord, aliceInquirer: HolderInquirer) {
|
||||
this.turnListenerOn()
|
||||
await aliceInquirer.acceptProofRequest(proofRecord)
|
||||
this.turnListenerOff()
|
||||
await aliceInquirer.processAnswer()
|
||||
}
|
||||
|
||||
public proofRequestListener(alice: Holder, aliceInquirer: HolderInquirer) {
|
||||
alice.agent.events.on(ProofEventTypes.ProofStateChanged, async ({ payload }: ProofStateChangedEvent) => {
|
||||
if (payload.proofRecord.state === ProofState.RequestReceived) {
|
||||
await this.newProofRequestPrompt(payload.proofRecord, aliceInquirer)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public proofAcceptedListener(faber: Issuer, faberInquirer: IssuerInquirer) {
|
||||
faber.agent.events.on(ProofEventTypes.ProofStateChanged, async ({ payload }: ProofStateChangedEvent) => {
|
||||
if (payload.proofRecord.state === ProofState.Done) {
|
||||
await faberInquirer.processAnswer()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public async newAcceptedPrompt(title: string, faberInquirer: IssuerInquirer) {
|
||||
this.turnListenerOn()
|
||||
await faberInquirer.exitUseCase(title)
|
||||
this.turnListenerOff()
|
||||
await faberInquirer.processAnswer()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
export enum Color {
|
||||
Green = `\x1b[32m`,
|
||||
Red = `\x1b[31m`,
|
||||
Purple = `\x1b[35m`,
|
||||
Reset = `\x1b[0m`,
|
||||
}
|
||||
|
||||
export enum Output {
|
||||
NoConnectionRecordFromOutOfBand = `\nNo connectionRecord has been created from invitation\n`,
|
||||
ConnectionEstablished = `\nConnection established!`,
|
||||
MissingConnectionRecord = `\nNo connectionRecord ID has been set yet\n`,
|
||||
ConnectionLink = `\nRun 'Receive connection invitation' in Alice and paste this invitation link:\n\n`,
|
||||
Exit = 'Shutting down agent...\nExiting...',
|
||||
}
|
||||
|
||||
export enum Title {
|
||||
OptionsTitle = '\nOptions:',
|
||||
InvitationTitle = '\n\nPaste the invitation url here:',
|
||||
MessageTitle = '\n\nWrite your message here:\n(Press enter to send or press q to exit)\n',
|
||||
ConfirmTitle = '\n\nAre you sure?',
|
||||
CredentialOfferTitle = '\n\nCredential offer received, do you want to accept it?',
|
||||
ProofRequestTitle = '\n\nProof request received, do you want to accept it?',
|
||||
}
|
||||
|
||||
export const greenText = (text: string, reset?: boolean) => {
|
||||
if (reset) return Color.Green + text + Color.Reset
|
||||
|
||||
return Color.Green + text
|
||||
}
|
||||
|
||||
export const purpleText = (text: string, reset?: boolean) => {
|
||||
if (reset) return Color.Purple + text + Color.Reset
|
||||
return Color.Purple + text
|
||||
}
|
||||
|
||||
export const redText = (text: string, reset?: boolean) => {
|
||||
if (reset) return Color.Red + text + Color.Reset
|
||||
|
||||
return Color.Red + text
|
||||
}
|
||||
Loading…
Reference in New Issue