Usage
Installation
- Web
- iOS
- Android
- React Native
For an example implementation, please refer to our react-wallet-chat example and demo
Initialize your WalletConnect ChatClient, using your Project ID
import { Core } from '@walletconnect/core'
import { SyncClient, SyncStore } from '@walletconnect/sync-client'
import { ChatClient } from '@walletconnect/chat-client'
// Initialize core separately to allow sharing it between sync and chat
const core = new Core({
projectId: '<YOUR PROJECT ID>'
})
// SyncClient enables syncing data across devices
const syncClient = await SyncClient.init({
projectId: '<YOUR PROJECT ID>',
core
})
const chatClient = await ChatClient.init({
core,
projectId: '<YOUR PROJECT ID>',
keyserverUrl: '<YOUR KEYSERVER URL: eg: https://keys.walletconnect.com',
syncClient,
SyncStoreController: SyncStore
})
Set up listeners for chat-related events
chatClient.on('chat_invite', async event => {
// React to an incoming invite to chat.
})
chatClient.on('chat_invite_accepted', async event => {
// React to your peer joining a given chat.
})
chatClient.on('chat_invite_rejected', async event => {
// React to your peer declining your invite
})
chatClient.on('chat_message', event => {
// React to an incoming messages for a given chat.
})
chatClient.on('chat_ping', event => {
// React to an incoming ping event from a peer on a given chat topic.
})
chatClient.on('chat_left', event => {
// React to a peer leaving a given chat.
})
Register your blockchain address with the key server
To allow for other Chat SDK user's to invite you to a chat, you need to register your account with the public key server.
The key server expects a full CAIP-2 account for the address (as shown below).
onSign
will be used to sign the identity key using a wallet, one could supply wagmi
's signMessage function here for example.
await chatClient.register({ account: `eip155:1:0xa6de541...`, onSign: async () => {} })
Inviting another peer to chat
To send a chat invitation to a peer, you can call .invite()
with your account and an invite
object:
const inviteePublicKey = await chatClient.resolve({ account: 'eip155:1:0xf5d44...' })
await chatClient.invite({
message: "Hey, Let's chat!,
inviterAccount: `eip155:1:0xa6de541...`, // your CAIP-2 formatted account that you registered previously.
inviteeAccount: 'eip155:1:0xf5d44...', // the CAIP-2 formatted account of the recipient.
inviteePublicKey
});
Accepting or rejecting an incoming invite
When your client receives an invite from a peer, you're able to accept/reject it by calling .accept()/.reject()
with the invite's id
.
await chatClient.accept({ id: invite.id })
// or
await chatClient.reject({ id: invite.id })
Sending a chat message
To send a message to a peer in an established chat thread (i.e. after you or the peer have accepted an invite),
you can call .message()
with the following parameters:
await chatClient.message({
topic: chatThread.topic,
message: 'Hey, good to hear from you!',
authorAccount: `eip155:1:0xa6de541...`, // your CAIP-2 formatted account that you registered previously.
timestamp: Date.now()
})
Leaving a chat thread
To leave an established chat thread (deleting the thread and its associated messages), call .leave()
with
the chat thread's topic:
await chatClient.leave({ topic: chatThread.topic })
Make sure what you properly configure Relay Client first
Configure Chat Client
Chat.configure(account: Account("eip155:56:0xe5EeF1368781911d265fDB6946613dA61915a501")!)
Use client instance
Chat client instance is a singleton and you can access it by calling
Chat.instance
Chat Client interface
public protocol ChatClient {
/// Socket connection status publisher
var socketConnectionStatusPublisher: AnyPublisher<SocketConnectionStatus, Never> { get }
/// Storage updates publishers
var messagesPublisher: AnyPublisher<[Message], Never> { get }
var receivedInvitesPublisher: AnyPublisher<[ReceivedInvite], Never> { get }
var sentInvitesPublisher: AnyPublisher<[SentInvite], Never> { get }
var threadsPublisher: AnyPublisher<[Thread], Never> { get }
/// Events publishers
var newMessagePublisher: AnyPublisher<Message, Never> { get }
var newReceivedInvitePublisher: AnyPublisher<ReceivedInvite, Never> { get }
var newSentInvitePublisher: AnyPublisher<SentInvite, Never> { get }
var newThreadPublisher: AnyPublisher<Thread, Never> { get }
/// Invitation handling publishers
var acceptPublisher: AnyPublisher<(String, SentInvite), Never> { get }
var rejectPublisher: AnyPublisher<SentInvite, Never> { get }
/// Registers a blockchain account with an identity key if not yet registered on this client
/// Registers invite key if not yet registered on this client and starts listening on invites if private is false
/// - Parameter onSign: Callback for signing CAIP-122 message to verify blockchain account ownership
/// - Returns: Returns the public identity key
@discardableResult
func register(account: Account,
isPrivate: Bool = false,
onSign: @escaping SigningCallback
) async throws -> String
/// Unregisters a blockchain account with previously registered identity key
/// Must not unregister invite key but must stop listening for invites
/// - Parameter onSign: Callback for signing CAIP-122 message to verify blockchain account ownership
func unregister(account: Account, onSign: @escaping SigningCallback) async throws
/// Queries the keyserver with a blockchain account
/// - Parameter account: CAIP10 blockchain account
/// - Returns: Returns the invite key
func resolve(account: Account) async throws -> String
/// Sends a chat invite
/// Creates and stores SentInvite with `pending` state
/// - Parameter invite: An Invite object
/// - Returns: Returns an invite id
@discardableResult
func invite(invite: Invite) async throws -> Int64
/// Unregisters an invite key from keyserver
/// Stops listening for invites
/// - Parameter account: CAIP10 blockchain account
func goPrivate(account: Account) async throws
/// Registers an invite key if not yet registered on this client from keyserver
/// Starts listening for invites
/// - Parameter account: CAIP10 blockchain account
/// - Returns: The public invite key
func goPublic(account: Account) async throws
/// Accepts a chat invite by id from account specified as inviteeAccount in Invite
/// - Parameter inviteId: Invite id
/// - Returns: Thread topic
@discardableResult
func accept(inviteId: Int64) async throws -> String
/// Rejects a chat invite by id from account specified as inviteeAccount in Invite
/// - Parameter inviteId: Invite id
func reject(inviteId: Int64) async throws
/// Sends a chat message to an active chat thread from account specified as selfAccount in Thread
/// - Parameters:
/// - topic: thread topic
/// - message: chat message
func message(topic: String, message: String) async throws
/// Ping its peer to evaluate if it's currently online
/// - Parameter topic: chat thread topic
func ping(topic: String)
/// Leaves a chat thread and stops receiving messages
/// - Parameter topic: chat thread topic
func leave(topic: String) async throws
/// Sets peer account with public key
/// - Parameter account: CAIP10 blockchain account
/// - Parameter publicKey: Account associated publicKey hex string
func setContact(account: Account, publicKey: String) async throws
/// Get received invites list
func getReceivedInvites() -> [ReceivedInvite]
/// Get sent invites list
func getSentInvites() -> [SentInvite]
/// Get threads list
func getThreads() -> [Thread]
/// Get messages list
func getMessages(topic: String) -> [Message]
}
Chat Client Objects
ReceivedInvite
public struct ReceivedInvite: Codable, Equatable {
public enum Status: String {
case pending
case rejected
case approved
}
public let id: Int64
public let message: String
public let inviterAccount: Account
public let inviteeAccount: Account
public let inviterPublicKey: String
public let inviteePublicKey: String
public let timestamp: UInt64
public var status: Status
}
SentInvite
public struct SentInvite: Codable, Equatable {
public enum Status: String, Codable, Equatable {
case pending
case approved
case rejected
}
public let id: Int64
public let message: String
public let inviterAccount: Account
public let inviteeAccount: Account
public let timestamp: UInt64
public var status: Status
}
Message
public struct Message: Codable, Equatable {
public struct Media: Codable, Equatable {
let type: String
let data: String
}
public let topic: String
public let message: String
public let authorAccount: Account
public let timestamp: UInt64
public let media: Media?
}
Thread
public struct Thread: Codable, Equatable {
public let topic: String
public let selfAccount: Account
public let peerAccount: Account
}
Methods
Register Identity at a Keyserver
In order to use Chat SDK account must register identity in Keyserver. To verify ownership over blockchain account when registering identities in Keyserver user's must sign message provided on onSign(message: String)
callback. Currently only EIP191
signatures are supported in Keyserver
Chat.instance.register
func register(account: Account, privateKey: String) async throws {
_ = try await Chat.instance.register(account: account) { [unowned self] message in
let signature = self.onSign(message: message, privateKey: privateKey)
return SigningResult.signed(signature)
}
}
func onSign(message: String, privateKey: String) -> CacaoSignature {
let privateKey = Data(hex: privateKey)
let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create()
return try! signer.sign(message: message, privateKey: privateKey, type: .eip191)
}
To not be discoverable to everyone, set private
param to true
. If this flag is true
then only identity is restored in Keyserver. Parameter private
is set to false
by default.
Resolve invitee public invite key with blockchain address
In order to invite someone for a chat conversation, inviter must know invitee public invite key. To resolve a invitee's public invite key that is required to invite into a chat thread, call Chat.instance.resolve
Chat.instance.resolve
func resolve(account: Account) async throws -> String {
return try await Chat.instance.resolve(account: account)
}
Invite into Chat Thread
After acquiring the invitee's public key, you can send an invitation. Invitations can include short messages to encourage the invitee to accept. To send an invitation, use the Chat.instance.invite
method. Sent invitations are stored and can be retrieved using the Chat.instance.getSentInvites
.
Chat.instance.invite
func invite(inviterAccount: Account, inviteeAccount: Account, message: String) async throws {
let inviteePublicKey = try await Chat.instance.resolve(account: inviteeAccount)
let invite = Invite(message: message, inviterAccount: inviterAccount, inviteeAccount: inviteeAccount, inviteePublicKey: inviteePublicKey)
try await Chat.instance.invite(invite: invite)
}
The invitee receives invitations via the newReceivedInvitePublisher
, which provides the necessary data to respond to the invitation. Received invitations are stored and can be retrieved using the Chat.instance.getReceivedInvites
.
Accepting and Rejecting an Invite
The invitee has the option to accept or reject the invitation. To accept an invitation, call the Chat.instance.accept
method. To reject an invitation, call the Chat.instance.reject
method.
Chat.instance.accept
func accept(invite: ReceivedInvite) async throws {
try await Chat.instance.accept(inviteId: invite.id)
}
Chat.instance.reject
func reject(invite: ReceivedInvite) async throws {
try await Chat.instance.reject(inviteId: invite.id)
}
Inviter receives invite response on either acceptPublisher
when invitee accepted the invite or on rejectPublisher
when invitee rejected the invite.
Sending a Chat Message
After successful invite inviter and invitee can E2EE direct messages with attached media. To send message call Chat.instance.message
method.
Chat.instance.message
func sendMessage(topic: String, message: String) async throws {
try await Chat.instance.message(topic: topic, message: message)
}
Leaving a Chat Thread
Calling Chat.instance.leave
with a chat thread topic causes the caller to stop receiving chat messages, removal of thread in storage and removal of chat messages on given thread in storage.
Chat.instance.leave
func leave(thread Thread) async throws {
try await Chat.instance.leave(topic: thread.topic)
}
Going Private
If you don't want your account to be discoverable to everyone, you can remove its public invite key from the Keyserver. Calling the Chat.instance.goPrivate
method means that your account will no longer listen for incoming invitations.
Chat.instance.goPrivate
func goPrivate(account: Account) async throws {
try await Chat.instance.goPrivate(account: account)
}
Going Public
If you want your account to be discoverable to everyone, you can add its public invite key to the Keyserver. Calling the ChatClient.goPublic
method means that your account will start listening for incoming invitations.
Chat.instance.goPublic
func goPublic(account: Account, privateKey: String) async throws {
try await Chat.instance.goPublic(account: account)
}
Unregistering Identity
To stop using the Chat SDK on a device, the account should call the Chat.instance.unregister
method. This removes the identity key that was assigned to the device.
Chat.instance.unregister
func unregister(account: Account, privateKey: String) async throws {
try await Chat.instance.unregister(account: account) { message in
let signature = self.onSign(message: message, privateKey: privateKey)
return SigningResult.signed(signature)
}
}
func onSign(message: String, privateKey: String) -> CacaoSignature {
let privateKey = Data(hex: privateKey)
let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create()
return try! signer.sign(message: message, privateKey: privateKey, type: .eip191)
}
Getting Received Invitations
Clients can retrieve all received invitations for a given account by calling Chat.instance.getReceivedInvites
. ReceivedInvite
contains a status of type ReceivedInvite.Status
to describe whether the invitation is still pending, was rejected, or has been approved.
Chat.instance.getReceivedInvites
func getReceivedInvites() -> [ReceivedInvite] {
return Chat.instance.getReceivedInvites()
}
Getting Sent Invites
Clients can retrieve all sent invitations for a given account by calling Chat.instance.getSentInvites
. SentInvite
contains a status of type SentInvite.Status
to describe whether the invitation is still pending, was rejected, or has been approved.
Chat.instance.getSentInvites
func getSentInvites() -> [SentInvite] {
return Chat.instance.getSentInvites()
}
Getting Threads
Clients can retrieve all threads for a given account by calling the Chat.instance.getThreads
method. The Thread]
object contains data on the topic that two accounts are communicating about.
Chat.instance.getThreads
func getThreads() -> [WalletConnectChat.Thread] {
return Chat.instance.getThreads()
}
Getting Messages
Clients can fetch all messages for given thread by calling Chat.instance.getMessages
. Message
contains data necessary to display a message. Message.Media
can be attached to message for versatility.
Chat.instance.getMessages
func getMessages(thread: WalletConnectChat.Thread) -> [Message] {
Chat.instance.getMessages(topic: thread.topic)
}
Chat SDK allows E2EE direct messaging between users, using their wallet address.
Chat Sample App
We recommend looking at example implementations of Chat Sample at our Kotlin GitHub repository
ChatClient
ChatClient
is an object that interacts with the Chat SDK and implements the ChatInterface. It contains asynchronous events that notify you of occurrences, such as receiving a message, and methods that you can use with the Chat SDK, such as sending messages.
ChatInterface
interface ChatInterface {
interface ChatDelegate {
fun onInvite(onInvite: Chat.Model.Events.OnInvite)
fun onInviteAccepted(onInviteAccepted: Chat.Model.Events.OnInviteAccepted)
fun onInviteRejected(onInviteRejected: Chat.Model.Events.OnInviteRejected)
fun onMessage(onMessage: Chat.Model.Events.OnMessage)
fun onLeft(onLeft: Chat.Model.Events.OnLeft)
fun onConnectionStateChange(state: Chat.Model.ConnectionState)
fun onError(error: Chat.Model.Error)
}
fun setChatDelegate(delegate: ChatDelegate)
fun initialize(init: Chat.Params.Init, onError: (Chat.Model.Error) -> Unit)
fun register(register: Chat.Params.Register, listener: Chat.Listeners.Register)
fun unregister(unregister: Chat.Params.Unregister, listener: Chat.Listeners.Unregister)
fun resolve(resolve: Chat.Params.Resolve, listener: Chat.Listeners.Resolve)
fun goPrivate(goPrivate: Chat.Params.GoPrivate, onSuccess: () -> Unit, onError: (Chat.Model.Error) -> Unit)
fun goPublic(goPublic: Chat.Params.GoPublic, onSuccess: (String) -> Unit, onError: (Chat.Model.Error) -> Unit)
fun invite(invite: Chat.Params.Invite, onSuccess: (Long) -> Unit, onError: (Chat.Model.Error) -> Unit)
fun accept(accept: Chat.Params.Accept, onSuccess: (String) -> Unit, onError: (Chat.Model.Error) -> Unit)
fun reject(reject: Chat.Params.Reject, onError: (Chat.Model.Error) -> Unit)
fun message(message: Chat.Params.Message, onError: (Chat.Model.Error) -> Unit)
fun ping(ping: Chat.Params.Ping, onSuccess: (String) -> Unit, onError: (Chat.Model.Error) -> Unit)
fun leave(leave: Chat.Params.Leave, onError: (Chat.Model.Error) -> Unit)
fun setContact(setContact: Chat.Params.SetContact, onError: (Chat.Model.Error) -> Unit)
fun getReceivedInvites(getReceivedInvites: Chat.Params.GetReceivedInvites): Map<Long, Chat.Model.Invite.Received>
fun getSentInvites(getSentInvites: Chat.Params.GetSentInvites): Map<Long, Chat.Model.Invite.Sent>
fun getThreads(getThreads: Chat.Params.GetThreads): Map<String, Chat.Model.Thread>
fun getMessages(getMessages: Chat.Params.GetMessages): List<Chat.Model.Message>
}
Initialize Chat Client
val projectId = "" // Get Project ID at https://cloud.walletconnect.com/
val relayUrl = "relay.walletconnect.com"
val serverUrl = "wss://$relayUrl?projectId=$projectId"
val connectionType = ConnectionType.AUTOMATIC or ConnectionType.MANUAL
val appMetaData = Core.Model.AppMetaData(
name = "Chat Sample",
description = "Chat Sample description",
url = "Chat Sample URL",
icons = /*list of icon URL strings*/,
redirect = "kotlin-chat-wc:/request" // Custom Redirect URI
)
CoreClient.initialize(relayServerUrl = serverUrl, connectionType = connectionType, application = this, metaData = appMetaData)
val init = Chat.Params.Init(coreClient = CoreClient)
ChatClient.initialize(init) { error ->
// Error will be thrown if there's an issue during initialization
}
To initialize the Chat client, create a Chat.Params.Init
object in the Application.onCreate
method of the Android Application class with the Core Client. Then, pass the Chat.Params.Init
object to the ChatClient initialization function.
ChatClient.ChatDelegate
val chatDelegate = object : ChatClient.ChatDelegate {
override fun onInvite(onInvite: Chat.Model.Events.OnInvite) {
// Triggered when a new invite is received
}
override fun onInviteAccepted(onInviteAccepted: Chat.Model.Events.OnInviteAccepted) {
// Triggered when a new chat thread joined
}
override fun onInviteRejected(onInviteRejected: Chat.Model.Events.OnInviteRejected) {
// Triggered when an invite is rejected by the other peer
}
override fun onMessage(onMessage: Chat.Model.Events.OnMessage) {
// Triggered when a new chat message is received
}
override fun onLeft(onLeft: Chat.Model.Events.OnLeft) {
// Triggered when a chat thread is left by a peer
}
override fun onConnectionStateChange(state: Chat.Model.ConnectionState) {
//Triggered whenever the connection state with Relay Server is changed
}
override fun onError(error: Chat.Model.Error) {
// Triggered whenever there is an issue inside the SDK
// I.e failure to subscribe to a topic
}
}
ChatClient.setChatDelegate(chatDelegate)
The ChatClient needs a ChatClient.ChatDelegate
passed to it for it to be able to expose asynchronous updates sent from the other peer.
Chat.Model.Events
The contents of ChatClient.ChatDelegate
callback functions are of type Chat.Model.Events
for chat specific events and Chat.Model.ConnectionState
or Chat.Model.Error
for Relay Server specific events.
Event Structures
// error of onError(error)
data class Error(val throwable: Throwable) : Model()
// state of onConnectionStateChange(state)
data class ConnectionState(val isAvailable: Boolean) : Model()
sealed class Events : Model() {
//onInvite corresponds to ChatDelegate.onInvite() parameter
data class OnInvite(val invite: Invite.Received) : Events()
//onInviteAccepted corresponds to ChatDelegate.onInviteAccepted() parameter
data class OnInviteAccepted(val topic: String, val invite: Chat.Model.Invite.Sent) : Events()
//onInviteRejected corresponds to ChatDelegate.onInviteRejected() parameter
data class OnInviteRejected(val invite: Chat.Model.Invite.Sent) : Events()
//onMessage corresponds to ChatDelegate.onMessage() parameter
data class OnMessage(val message: Message) : Events()
//onLeft corresponds to ChatDelegate.onLeft() parameter
data class OnLeft(val topic: String) : Events()
}
Invite.Received
// Invite.Received data class from OnInvite event
data class Received(
override val id: Long,
override val inviterAccount: Type.AccountId,
override val inviteeAccount: Type.AccountId,
override val message: Type.InviteMessage,
override val inviterPublicKey: String,
override val inviteePublicKey: String,
override val status: Type.InviteStatus,
) : Invite
Invite.Sent
// Invite.Sent data class from OnInviteAccepted or OnInviteRejected event
data class Sent(
override val id: Long,
override val inviterAccount: Type.AccountId,
override val inviteeAccount: Type.AccountId,
override val message: Type.InviteMessage,
override val inviterPublicKey: String,
override val inviteePublicKey: String,
override val status: Type.InviteStatus,
) : Invite
Message
// Message data class from OnMessage event
data class Message(
val topic: String,
val message: Type.ChatMessage,
val authorAccount: Type.AccountId,
val timestamp: Long,
val media: Media?,
) : Model()
// Media data class from Message data class
data class Media(
val type: String,
val data: Type.MediaData,
) : Model()
Methods
Register Identity at a Keyserver
In order to use Chat SDK account must register identity in Keyserver. To verify ownership over blockchain account when registering identities in Keyserver user's must sign message provided on onSign(message: String)
callback. Currently only EIP191
signatures are supported in Keyserver
ChatClient.register
val params = Chat.Params.Register(account = Chat.Type.AccountId(/*[CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md) compatible accountId*/))
ChatClient.register(params, object : Chat.Listeners.Register {
override fun onSign(message: String): Chat.Model.Cacao.Signature? {
// Message to be signed. When user decides to sign message use CacaoSigner to sign message.
// CacaoSigner is a util for easy message signing.
return CacaoSigner.sign(message, /*privateKey*/, SignatureType.EIP191)
// When users decides to not sign message return null
return null
}
override fun onError(error: Chat.Model.Error) {
// Error while registering an address
}
override fun onSuccess(publicKey: String) {
// Identity key registered successfully
}
})
To not be discoverable to everyone, set private
in Chat.Params.Register
to true
. If this flag is true
then only identity is restored in Keyserver. Parameter private
is set to false
by default.
Chat.Params.Register with private = false
val params = Chat.Params.Register(account = Chat.Type.AccountId(/*[CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md) compatible accountId*/), private = true)
Resolve invitee public invite key with blockchain address
In order to invite someone for a chat conversation, inviter must know invitee public invite key. To resolve a invitee's public invite key that is required to invite into a chat thread, call ChatClient.resolve
method.
ChatClient.resolve
val params = Chat.Params.Resolve(account = Chat.Type.AccountId(/*[CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md) compatible accountId*/))
ChatClient.resolve(params), object : Chat.Listeners.Resolve {
override fun onError(error: Chat.Model.Error) {
//Error occurred
}
override fun onSuccess(publicKey: String) {
//Public key found for given account address
}
})
Invite into Chat Thread
After acquiring the invitee's public key, you can send an invitation. Invitations can include short messages to encourage the invitee to accept. To send an invitation, use the ChatClient.invite method. Sent invitations are stored and can be retrieved using the ChatClient.getSentInvites method, which is described in the Getting Sent Invitations section.
ChatClient.invite
val params = Chat.Params.Invite(inviterAccount = Chat.Type.AccountId(/*[CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md) compatible accountId*/)), inviteeAccount = Chat.Type.AccountId(/*[CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md) compatible accountId*/), message = Chat.Type.InviteMessage(/* Opening message string that's 200 char limited */), inviteePublicKey = /*Resolved public invite key from ChatClient.resolve() method*/)
ChatClient.invite(
params,
onSuccess = { inviteId -> /*Id of the invite*/ },
onError = { error -> /* Error when inviting */) }
)
The invitee receives invitations via the ChatClient.ChatDelegate.onInvite callback, which provides the necessary data to respond to the invitation. Received invitations are stored and can be retrieved using the ChatClient.getReceivedInvites method, which is described in the Getting Received Invitations section.
Accepting and Rejecting an Invite
The invitee has the option to accept or reject the invitation. To accept an invitation, call the ChatClient.accept method. To reject an invitation, call the ChatClient.reject method.
ChatClient.accept
val acceptParams = Chat.Params.Accept(inviteId = /*Id of invite received onInvite() event in ChatDelegate*/)
ChatClient.accept(acceptParams,
onSuccess = { threadTopic -> /* Thread topic */ },
onError = { error -> /* Error when accepting */ })
ChatClient.reject
val params = Chat.Params.Reject(inviteId = /*Id of invite received onInvite() event in ChatDelegate*/)
ChatClient.reject(params, onError = { error -> /* Error when rejecting */ })
Inviter receives invite response on either ChatClient.ChatDelegate.onInviteAccepted
when invitee accepted the invite or on ChatClient.ChatDelegate.onInviteRejected
when invitee rejected the invite.
Sending a Chat Message
After successful invite inviter and invitee can E2EE direct messages with attached media. To send message call ChatClient.message
method.
ChatClient.message
val threadTopic = /*thread topic*/
val message = Chat.Type.ChatMessage(message = /* Message string that's 1000 char limited */)
// Optional media parameter
val media = Chat.Type.Media(type = /* Type of media */, data = /* Media data string that's 500 chat limited */)
val params = Chat.Params.Message(threadTopic, message, media)
ChatClient.message(params) { error -> /* Error while sending a message */ }
Leaving a Chat Thread
Calling `ChatClient.leave with a chat thread topic causes the caller to stop receiving chat messages, removal of thread in storage and removal of chat messages on given thread in storage.
ChatClient.leave
val params = Chat.Params.Leave(topic = /* Thread topic*/))
ChatClient.leave(params) { error -> /* Error while leaving a thread */ }
Going Private
If you don't want your account to be discoverable to everyone, you can remove its public invite key from the Keyserver. Calling the ChatClient.goPrivate method means that your account will no longer listen for incoming invitations.
ChatClient.goPrivate
val params = Chat.Params.GoPrivate(account = Chat.Type.AccountId(/*[CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md) compatible accountId*/))
ChatClient.goPrivate(
params,
onSuccess = { /* Success callback */ },
onError = { error -> /* Error while going private */ }
)
Going Public
If you want your account to be discoverable to everyone, you can add its public invite key to the Keyserver. Calling the ChatClient.goPublic method means that your account will start listening for incoming invitations.
ChatClient.goPublic
val params = Chat.Params.GoPublic(account = Chat.Type.AccountId(/*[CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md) compatible accountId*/))
ChatClient.goPublic(
params,
onSuccess = { /* Success callback */ },
onError = { error -> /* Error while going Public */ }
)
Unregistering Identity
To stop using the Chat SDK on a device, the account should call the ChatClient.unregister method. This removes the identity key that was assigned to the device.
ChatClient.unregister
val params = Chat.Params.Unregister(account = Chat.Type.AccountId(/*[CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md) compatible accountId*/))
ChatClient.unregister(params, object : Chat.Listeners.Unregister {
override fun onSign(message: String): Chat.Model.Cacao.Signature? {
// Message to be signed. When user decides to sign message use CacaoSigner to sign message.
// CacaoSigner is a util for easy message signing.
return CacaoSigner.sign(message, /*privateKey*/, SignatureType.EIP191)
// When users decides to not sign message return null
return null
}
override fun onError(error: Chat.Model.Error) {
//Error while unregistering an address
}
override fun onSuccess(publicKey: String) {
//Identity key unregistered successfully
}
})
Getting Received Invitations
Clients can retrieve all received invitations for a given account by calling ChatClient.getReceivedInvites. Invite.Received contains a status of type Type.InviteStatus to describe whether the invitation is still pending, was rejected, or has been approved.
Type.InviteStatus
enum class InviteStatus : Type {
PENDING, // Invite that was not yet responded to
REJECTED, // Invite that was rejected by client
APPROVED // Invite that was approved by client
}
ChatClient.getReceivedInvites
val params = Chat.Params.GetReceivedInvites(account = Chat.Type.AccountId(/*[CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md) compatible accountId*/))
val receivedInvites: Map<Long, Chat.Model.Invite.Received> = ChatClient.getReceivedInvites(params)
Getting Sent Invites
Clients can retrieve all sent invitations for a given account by calling ChatClient.getSentInvites
. Invite.Sent
contains a status of type Type.InviteStatus
to describe whether the invitation is still pending, was rejected, or has been approved.
Type.InviteStatus
enum class InviteStatus : Type {
PENDING, // Invite that was not yet responded to
REJECTED, // Invite that was rejected by client
APPROVED // Invite that was approved by client
}
Invite.Sent
// Invite.Sent data class
data class Sent(
override val id: Long,
override val inviterAccount: Type.AccountId,
override val inviteeAccount: Type.AccountId,
override val message: Type.InviteMessage,
override val inviterPublicKey: String,
override val inviteePublicKey: String,
override val status: Type.InviteStatus,
) : Invite
ChatClient.getSentInvites
val params = Chat.Params.GetSentInvites(account = Chat.Type.AccountId(/*[CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md) compatible accountId*/))
val sentInvites: Map<Long, Chat.Model.Invite.Sent> = ChatClient.getSentInvites(params)
Getting Threads
Clients can retrieve all threads for a given account by calling the ChatClient.getThreads method. The Model.Thread object contains data on the topic that two accounts are communicating about.
Model.Thread
data class Thread(
val topic: String,
val selfAccount: Type.AccountId,
val peerAccount: Type.AccountId,
) : Model()
ChatClient.getThreads
val params = Chat.Params.GetThreads(account = Chat.Type.AccountId(/*[CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md) compatible accountId*/))
val threads: Map<String, Chat.Model.Thread> = ChatClient.getThreads(params)
Getting Messages
Clients can fetch all messages for given thread by calling ChatClient.getMessages
. Model.Message
contains data necessary to display a message. Model.Media
can be attached to message for versatility.
Model.Message
data class Message(
val topic: String,
val message: Type.ChatMessage,
val authorAccount: Type.AccountId,
val timestamp: Long,
val media: Media?,
) : Model()
Model.Media
data class Media(
val type: String,
val data: Type.MediaData,
) : Model()
ChatClient.getMessages
val params = Chat.Params.GetMessages(topic = /*Thread topic*/))
val messages: List<Chat.Model.Message> = ChatClient.getMessages(params)
Initialize your WalletConnect ChatClient, using your Project ID
import { Core } from '@walletconnect/core'
import { SyncClient, SyncStore } from '@walletconnect/sync-client'
import { ChatClient } from '@walletconnect/chat-client'
// Initialize core separately to allow sharing it between sync and chat
const core = new Core({
projectId: '<YOUR PROJECT ID>'
})
// SyncClient enables syncing data across devices
const syncClient = await SyncClient.init({
projectId: '<YOUR PROJECT ID>',
core
})
const chatClient = await ChatClient.init({
core,
projectId: '<YOUR PROJECT ID>',
keyserverUrl: '<YOUR KEYSERVER URL: eg: https://keys.walletconnect.com',
syncClient,
SyncStoreController: SyncStore
})
Set up listeners for chat-related events
chatClient.on('chat_invite', async event => {
// React to an incoming invite to chat.
})
chatClient.on('chat_invite_accepted', async event => {
// React to your peer joining a given chat.
})
chatClient.on('chat_invite_rejected', async event => {
// React to your peer declining your invite
})
chatClient.on('chat_message', event => {
// React to an incoming messages for a given chat.
})
chatClient.on('chat_ping', event => {
// React to an incoming ping event from a peer on a given chat topic.
})
chatClient.on('chat_left', event => {
// React to a peer leaving a given chat.
})
Register your blockchain address with the key server
To allow for other Chat SDK user's to invite you to a chat, you need to register your account with the public key server.
The key server expects a full CAIP-2 account for the address (as shown below).
onSign
will be used to sign the identity key using a wallet, one could supply wagmi
's signMessage function here for example.
await chatClient.register({ account: `eip155:1:0xa6de541...`, onSign: async () => {} })
Inviting another peer to chat
To send a chat invitation to a peer, you can call .invite()
with your account and an invite
object:
const inviteePublicKey = await chatClient.resolve({ account: 'eip155:1:0xf5d44...' })
await chatClient.invite({
message: "Hey, Let's chat!,
inviterAccount: `eip155:1:0xa6de541...`, // your CAIP-2 formatted account that you registered previously.
inviteeAccount: 'eip155:1:0xf5d44...', // the CAIP-2 formatted account of the recipient.
inviteePublicKey
});
Accepting or rejecting an incoming invite
When your client receives an invite from a peer, you're able to accept/reject it by calling .accept()/.reject()
with the invite's id
.
await chatClient.accept({ id: invite.id })
// or
await chatClient.reject({ id: invite.id })
Sending a chat message
To send a message to a peer in an established chat thread (i.e. after you or the peer have accepted an invite),
you can call .message()
with the following parameters:
await chatClient.message({
topic: chatThread.topic,
message: 'Hey, good to hear from you!',
authorAccount: `eip155:1:0xa6de541...`, // your CAIP-2 formatted account that you registered previously.
timestamp: Date.now()
})
Leaving a chat thread
To leave an established chat thread (deleting the thread and its associated messages), call .leave()
with
the chat thread's topic:
await chatClient.leave({ topic: chatThread.topic })
Was this helpful?