Integrate Meeting Place for Dart
The Meeting Place integration guide provides a step-by-step overview for developers to integrate DIDComm-based communication into their applications. DIDComm enables secure, privacy-preserving, and verifiable messaging between decentralised identifiers (DIDs), allowing users to exchange messages securely and privately without relying on centralised intermediaries.

Sample screens from Affinidi Meeting Place Open-sourced App
Key Features
Set up a main profile and create aliases for different roles or contexts.
Share secure, customisable invites to connect individually or in groups.
Chat privately in peer or group settings with built-in security and privacy.
Prove your identity using verifiable credentials in conversations.
Use default messaging servers or set up your own for full control.
Requirements
- Dart SDK version 3.9.0 or later.
Required Packages
The following Dart packages are required to get started with the integration:
| Package | Description |
|---|---|
| Affinidi Meeting Place - Core SDK for Dart | The core package implementing the Control Plane and Mediator SDK, including managing multiple digital identities for better privacy and anonymity in digital interactions. |
| Affinidi Meeting Place - Chat SDK for Dart | This package is built on top of the core Meeting Place SDK that utilises Decentralised Identifier (DID) and DIDComm Messaging v2.1 protocol to send secure and private messages with end-to-end encryption. |
| SSI | A package that provides libraries and tools for implementing Self-Sovereign Identity (SSI). This is used to create the DID to represent a user’s identity. |
Sample Usage
Initialising the SDK
The integration starts with creating an Core SDK instance.
// create a Persistent wallet using SSI
final wallet = PersistentWallet(InMemoryKeyStore());
// create a RepositoryConfig
final storage = InMemoryStorage();
final repositoryConfig = RepositoryConfig(
connectionOfferRepository: ConnectionOfferRepositoryImpl(storage: storage),
groupRepository: GroupRepositoryImpl(storage: storage),
channelRepository: ChannelRepositoryImpl(storage: storage),
keyRepository: KeyRepositoryImpl(storage: storage),
);
// Define the mediator did
final mediatorDid = '<YOUR-MEDIATOR-DID>:.well-known';
// Define the serviceDID (control plane DID)
final serviceDid = '<YOUR-CONTROL-PLANE-DID>';
// Create an instance of Core SDK
final aliceSDK = await CoreSDK.create(
wallet: wallet,
repositoryConfig: repositoryConfig,
mediatorDid: mediatorDid,
serviceDid: serviceDid
);
// Register the device to receive push notification
await aliceSDK.registerForPushNotifications(const Uuid().v4());Publish an Offer
An offer pertains to an invitation for others to connect with you on Meeting Place.
The example below shows how to publish an offer to make you discoverable. You have an option to either provide a custom phrase or automatically generate random phrases in your offer.
// Publish an offer using Core SDK
final publishOfferResult = await aliceSDK.publishOffer(
offerName: '<YOUR-OFFER-NAME>',
offerDescription: '<YOUR-OFFER-DESC>',
customPhrase: '<YOUR-CUSTOM-PHRASE>', // if not provided, it will generate a random mnemonic
vCard: VCard(values: {}),
publishAsGroup: false,
validUntil: DateTime.now().toUtc().add(const Duration(minutes: 5)),
);
// Listen to Discovery Events Stream to receive updates about the published offer
final waitForInvitationAccept = Completer<DiscoveryStreamEvent>();
final waitForChannelActivity = Completer<DiscoveryStreamEvent>();
aliceSDK.discoveryEventsStream.listen((event) {
if (event.type == DiscoveryEventType.InvitationAccept) {
waitForInvitationAccept.complete(event);
}
if (event.type == DiscoveryEventType.ChannelActivity) {
if (!waitForChannelActivity.isCompleted) {
waitForChannelActivity.complete(event);
}
}
});
// Create a Mediator Channel to listen to discovery events about the published offer
final publishOfferMediatorChannel = await aliceSDK.subscribeToMediator(
publishOfferResult.connectionOffer.publishOfferDid,
deleteOnMediator: false,
);
// Use the Mediator Channel to listen to discovery events about the published offer
publishOfferMediatorChannel.stream.listen((data) async {
if (data.type.toString() == MeetingPlaceProtocol.connectionSetup.value) {
await aliceSDK.processDiscoveryEvents();
}
});Find an Offer
When an offer is published, other users can find and request to connect with you on Meeting Place by providing the passphrase of your offer. The example below shows how to find an offer by providing the mnemonic.
Note: It is required to initialise the Core SDK calling the
findOffermethod.
// Find the offer using Alice's mnemonic
final findOfferResult = await bobSDK.findOffer(mnemonic: '<ALICE-OFFER>');Accept an Offer
After finding an offer, a user can request to connect with another user by accepting the offer. When a user accepts an offer, it sends a notification requesting a connection, along with the requestor’s vCard.
The example below uses the Core SDK to send a connection request to the owner of the offer. It also sends a notification about the connection offer.
// Accept the offer to accept the invite
final acceptOfferResult = await bobSDK.acceptOffer(
connectionOffer: findOfferResult.connectionOffer!,
vCard: VCard(values: {}),
);
// Listen on discovery events stream to receive update on the accepted offer
final waitForOfferFinalised = Completer<DiscoveryStreamEvent>();
bobSDK.discoveryEventsStream.listen((event) {
if (event.type == DiscoveryEventType.OfferFinalised) {
waitForOfferFinalised.complete(event);
}
});
// Create a Mediator Channel to listen to discovery events about the accepted offer
final mediatorChannel = await bobSDK.subscribeToMediator(
acceptOfferResult.connectionOffer.acceptOfferDid!,
deleteOnMediator: false,
);
// Use the Mediator Channel to listen to discovery events about the accepted offer
mediatorChannel.stream.listen((data) async {
if (data.type.toString() == MeetingPlaceProtocol.connectionAccepted.value) {
await Future.delayed(Duration(seconds: 3));
await bobSDK.processDiscoveryEvents();
}
});
// Send a notification to Alice that Bob had requested to connect
await bobSDK.notifyAcceptance(
connectionOffer: acceptOfferResult.connectionOffer);Approve Connection Request
When a user sends a request to connect from an accepted offer, the offer owner should receive a notification to approve the request. This finalises the connection request, which signifies that both parties can start sending and receiving messages from each other.
The example below shows how a connection request is approved using Core SDK on the offerer side. It also establishes the permanent channel to listen to a channel inauguration event from the requrestor.
// Get the connection request event from Bob
final receivedEvent = await waitForInvitationAccept.future;
// Alice approves/accepts the connection request from Bob
final channel = await aliceSDK.approveConnectionRequest(
connectionOffer: publishOfferResult.connectionOffer,
channel: receivedEvent.channel,
);
// Setup the permanent channel
final permanentChannelDidMediatorChannel = await aliceSDK.subscribeToMediator(
channel.permanentChannelDid!,
deleteOnMediator: false,
);
// Listen to discovery events about channel inauguration from Bob
permanentChannelDidMediatorChannel.stream.listen((data) async {
if (data.type.toString() == MeetingPlaceProtocol.channelInauguration.value) {
await aliceSDK.processDiscoveryEvents();
}
});Start a Chat Session
Starting a chat session is a pre-requisite for both parties before they can start sending and receiving messages.
The example below uses the Chat SDK to create and start a chat session. The Chat SDK also provides event streaming to get events on messages sent and receive from the other party.
// Create an instance of ChatSDK
final aliceChatSDK = await ChatSDK.initialiseFromChannel(
channel,
coreSDK: aliceSDK,
chatRepository: ChatRepositoryImpl(storage: InMemoryStorage()),
options: ChatSDKOptions(chatPresenceSendInterval: 60),
);
// Start the chat session using the created ChatSDK
await aliceChatSDK.start();
// Setup the channel stream to get message events from the chat
await aliceChatSDK.channelSubscription.then((stream) {
stream!.listen((data) {
if (data.plainTextMessage != null) {
prettyJsonPrintYellow(
'Received message on chat stream',
data.plainTextMessage!.toJson(),
);
}
if (data.chatItem != null) {
prettyJsonPrintYellow(
'Received chat item on chat stream',
data.chatItem!.toJson(),
);
}
});
});Sending a Message
After initialising a chat session, a user can start sending messages to the other party.
The example below shows how to send a message using the ChatSDK.
// Send a plain text message to Alice
await bobChatSDK.sendTextMessage('Hi Alice!');Optionally, you could use the Core SDK to send a message to another user.
Note: The
sendMessage()method can be used to send other types of messages not limited to plain text but also commands or operations required by your application.
final bobSDK = await CoreSDK.create(...);
await bobSDK.sendMessage(
PlainTextMessage(
id: Uuid().v4(),
type: Uri.parse(ChatProtocol.chatMessage.value),
from: offerFinalisedEvent.channel.permanentChannelDid,
to: [offerFinalisedEvent.channel.otherPartyPermanentChannelDid!],
body: {'text': 'This is a custom message', 'seqNo': 100},
),
senderDid: offerFinalisedEvent.channel.permanentChannelDid!,
recipientDid: offerFinalisedEvent.channel.otherPartyPermanentChannelDid!,
);What’s Next
Glad to hear it! Please tell us how we can improve more.
Sorry to hear that. Please tell us how we can improve.
Thank you for sharing your feedback so we can improve your experience.