Skip to main content
Version: v0.4.x

Create a connection

In this tutorial we will create a connection as Acme Corp with Bob. We will start with setting up both their agents with the minimal configuration required to follow this tutorial. After the initialization we will then create an invitation as Acme Corp and send it over to Bob. Bob will then accept this invitation and at that point they have established a connection and they know how to reach each other for sending a basic message, issuing a credential, verifying a proof, etc.

info

This section assumes that

  1. You have set-up your development environment.
  2. You have basic knowledge of the required fields in the Agent Config

1. Setting up the agents

First for both agents we must setup and initialize an agent to work with. Depending on your target, React Native or Node.js, it might vary.

In this tutorial Bob will be in a React Native environment and Acme Corp in a Node.js environment.

Bob

For bob we need to setup a basic agent with a wallet, mediator and outbound transport.

🧔bob
const initializeBobAgent = async () => {
// Simple agent configuration. This sets some basic fields like the wallet
// configuration and the label. It also sets the mediator invitation url,
// because this is most likely required in a mobile environment.
const config: InitConfig = {
label: 'demo-agent-bob',
walletConfig: {
id: 'mainBob',
key: 'demoagentbob00000000000000000000',
},
}

// A new instance of an agent is created here
// Askar can also be replaced by the indy-sdk if required
const agent = new Agent({
config,
modules: {
askar: new AskarModule({ ariesAskar }),
connections: new ConnectionsModule({ autoAcceptConnections: true }),
},
dependencies: agentDependencies,
})

// Register a simple `WebSocket` outbound transport
agent.registerOutboundTransport(new WsOutboundTransport())

// Register a simple `Http` outbound transport
agent.registerOutboundTransport(new HttpOutboundTransport())

// Initialize the agent
await agent.initialize()

return agent
}

Acme

For Acme we need to setup a basic agent with a wallet, inbound and outbound transport.

🏢acme
const initializeAcmeAgent = async () => {
// Simple agent configuration. This sets some basic fields like the wallet
// configuration and the label.
const config: InitConfig = {
label: 'demo-agent-acme',
walletConfig: {
id: 'mainAcme',
key: 'demoagentacme0000000000000000000',
},
endpoints: ['http://localhost:3001'],
}

// A new instance of an agent is created here
// Askar can also be replaced by the indy-sdk if required
const agent = new Agent({
config,
modules: {
askar: new AskarModule({ ariesAskar }),
connections: new ConnectionsModule({ autoAcceptConnections: true }),
},
dependencies: agentDependencies,
})

// Register a simple `WebSocket` outbound transport
agent.registerOutboundTransport(new WsOutboundTransport())

// Register a simple `Http` outbound transport
agent.registerOutboundTransport(new HttpOutboundTransport())

// Register a simple `Http` inbound transport
agent.registerInboundTransport(new HttpInboundTransport({ port: 3001 }))

// Initialize the agent
await agent.initialize()

return agent
}

2. Creating an invitation

Now that we have setup both agents, we can create an invitation from Acme Corp.

This method will create an invitation using the legacy method according to 0434: Out-of-Band Protocol 1.1.

🏢acme
const createNewInvitation = async (agent: Agent) => {
const outOfBandRecord = await agent.oob.createInvitation()

return {
invitationUrl: outOfBandRecord.outOfBandInvitation.toUrl({ domain: 'https://example.org' }),
outOfBandRecord,
}
}

3. Receiving the invitation

After we have created the invitation we have to transmit it to the other agent. Common practise, when sending it to a holder, it to embed the url inside a QR code. This QR code can then be scanned by the holder, in this case Bob. After this, because both have set autoAcceptConnections to true, the connection is established.

🧔bob
const receiveInvitation = async (agent: Agent, invitationUrl: string) => {
const { outOfBandRecord } = await agent.oob.receiveInvitationFromUrl(invitationUrl)

return outOfBandRecord
}

4. (additional) listen to incoming connection responses

When you quickly want to use the event or the data of a response to a connection request, you can start an TODO: agent event listener.

Another use case for this would be to get the connectionRecord of the connection as it is only created when the invitation has been received by the other agent. The connectionRecord is very essential in processes like TODO: issuing a credential or TODO: verifying a proof.

The connectionRecord can also be retrieved with agent.connections.findAllByOutOfBandId(id), but with this method there is no way of knowing if the invitation has been received.

🏢acme
const setupConnectionListener = (agent: Agent, outOfBandRecord: OutOfBandRecord, cb: (...args: any) => void) => {
agent.events.on<ConnectionStateChangedEvent>(ConnectionEventTypes.ConnectionStateChanged, ({ payload }) => {
if (payload.connectionRecord.outOfBandId !== outOfBandRecord.id) return
if (payload.connectionRecord.state === DidExchangeState.Completed) {
// the connection is now ready for usage in other protocols!
console.log(`Connection for out-of-band id ${outOfBandRecord.id} completed`)

// Custom business logic can be included here
// In this example we can send a basic message to the connection, but
// anything is possible
cb()

// We exit the flow
process.exit(0)
}
})
}

5. Full code snippets

Below are both code snippets for each agent. These can be used as base but should be edited to fit your use case. The walletConfig.key must be changed as it can lead to other people knowing your "password" to your wallet.

import { AskarModule } from '@aries-framework/askar'
import {
Agent,
InitConfig,
ConnectionEventTypes,
ConnectionStateChangedEvent,
WsOutboundTransport,
HttpOutboundTransport,
DidExchangeState,
OutOfBandRecord,
ConnectionsModule,
} from '@aries-framework/core'
import { agentDependencies, HttpInboundTransport } from '@aries-framework/node'
import { ariesAskar } from '@hyperledger/aries-askar-nodejs'

const initializeBobAgent = async () => {
// Simple agent configuration. This sets some basic fields like the wallet
// configuration and the label. It also sets the mediator invitation url,
// because this is most likely required in a mobile environment.
const config: InitConfig = {
label: 'demo-agent-bob',
walletConfig: {
id: 'mainBob',
key: 'demoagentbob00000000000000000000',
},
}

// A new instance of an agent is created here
// Askar can also be replaced by the indy-sdk if required
const agent = new Agent({
config,
modules: {
askar: new AskarModule({ ariesAskar }),
connections: new ConnectionsModule({ autoAcceptConnections: true }),
},
dependencies: agentDependencies,
})

// Register a simple `WebSocket` outbound transport
agent.registerOutboundTransport(new WsOutboundTransport())

// Register a simple `Http` outbound transport
agent.registerOutboundTransport(new HttpOutboundTransport())

// Initialize the agent
await agent.initialize()

return agent
}

const initializeAcmeAgent = async () => {
// Simple agent configuration. This sets some basic fields like the wallet
// configuration and the label.
const config: InitConfig = {
label: 'demo-agent-acme',
walletConfig: {
id: 'mainAcme',
key: 'demoagentacme0000000000000000000',
},
endpoints: ['http://localhost:3001'],
}

// A new instance of an agent is created here
// Askar can also be replaced by the indy-sdk if required
const agent = new Agent({
config,
modules: {
askar: new AskarModule({ ariesAskar }),
connections: new ConnectionsModule({ autoAcceptConnections: true }),
},
dependencies: agentDependencies,
})

// Register a simple `WebSocket` outbound transport
agent.registerOutboundTransport(new WsOutboundTransport())

// Register a simple `Http` outbound transport
agent.registerOutboundTransport(new HttpOutboundTransport())

// Register a simple `Http` inbound transport
agent.registerInboundTransport(new HttpInboundTransport({ port: 3001 }))

// Initialize the agent
await agent.initialize()

return agent
}

const createNewInvitation = async (agent: Agent) => {
const outOfBandRecord = await agent.oob.createInvitation()

return {
invitationUrl: outOfBandRecord.outOfBandInvitation.toUrl({ domain: 'https://example.org' }),
outOfBandRecord,
}
}

const createLegacyInvitation = async (agent: Agent) => {
const { invitation } = await agent.oob.createLegacyInvitation()

return invitation.toUrl({ domain: 'https://example.org' })
}

const receiveInvitation = async (agent: Agent, invitationUrl: string) => {
const { outOfBandRecord } = await agent.oob.receiveInvitationFromUrl(invitationUrl)

return outOfBandRecord
}

const setupConnectionListener = (agent: Agent, outOfBandRecord: OutOfBandRecord, cb: (...args: any) => void) => {
agent.events.on<ConnectionStateChangedEvent>(ConnectionEventTypes.ConnectionStateChanged, ({ payload }) => {
if (payload.connectionRecord.outOfBandId !== outOfBandRecord.id) return
if (payload.connectionRecord.state === DidExchangeState.Completed) {
// the connection is now ready for usage in other protocols!
console.log(`Connection for out-of-band id ${outOfBandRecord.id} completed`)

// Custom business logic can be included here
// In this example we can send a basic message to the connection, but
// anything is possible
cb()

// We exit the flow
process.exit(0)
}
})
}


const run = async () => {
console.log('Initializing Bob agent...')
const bobAgent = await initializeBobAgent()
console.log('Initializing Acme agent...')
const acmeAgent = await initializeAcmeAgent()

console.log('Creating the invitation as Acme...')
const { outOfBandRecord, invitationUrl } = await createNewInvitation(acmeAgent)

console.log('Listening for connection changes...')
setupConnectionListener(acmeAgent, outOfBandRecord, () =>
console.log('We now have an active connection to use in the following tutorials')
)

console.log('Accepting the invitation as Bob...')
await receiveInvitation(bobAgent, invitationUrl)
}

export default run

void run()

Useful resources