Build a chat inbox
Create or build a client
Create an account signer
This code defines two functions that convert different types of Ethereum accounts—Externally Owned Accounts (EOAs) and Smart Contract Wallets (SCWs)—into a unified Signer
interface. This ensures that both account types conform to a common interface for message signing and deriving shared secrets as per MLS (Message Layer Security) requirements.
-
For an EOA, the
convertEOAToSigner
function creates a signer that can get the account address and sign messages and has placeholder methods for wallet type, chain ID, and block number.Browserimport type { Signer } from "@xmtp/browser-sdk"; const accountAddress = "0x..."; const signer: Signer = { getAddress: () => accountAddress, signMessage: async (message) => { // return value from a signing method here }, };
-
For an SCW, the
convertSCWToSigner
function similarly creates a signer but includes specific implementations for wallet type and chain ID, and an optional block number computation.Browserimport type { Signer } from "@xmtp/browser-sdk"; const accountAddress = "0x..."; const signer: Signer = { getAddress: () => accountAddress, signMessage: async (message) => { // return value from a signing method here }, // these methods are required for smart contract wallets // block number is optional getBlockNumber: () => undefined, // this example uses the Base chain getChainId: () => BigInt(8453), };
Create an XMTP client
Create an XMTP MLS client that can use the signing capabilities provided by the SigningKey
parameter. This SigningKey
links the client to the appropriate EOA or SCW.
import { Client, type Signer } from "@xmtp/browser-sdk";
const accountAddress = "0x...";
const signer: Signer = {
getAddress: () => accountAddress,
signMessage: async (message) => {
// return value from a signing method here
},
};
// this value should be generated once per installation and stored securely
const encryptionKey = window.crypto.getRandomValues(new Uint8Array(32));
const client = await Client.create(
signer,
encryptionKey,
options /* optional */
);
When an app first calls Client.create()
, a client creates a local database to manage messaging between the app and the network. In subsequent calls, it loads the existing database. The database is encrypted using the keys from the Signer
 interface.
To learn more about database operations, see the XMTP MLS protocol spec.
Configure an XMTP client
You can configure an XMTP client with these parameters of Client.create
:
Parameter | Default | Description |
---|---|---|
env | DEV | Connect to the specified XMTP network environment. Valid values include DEV , PRODUCTION , or LOCAL . For important details about working with these environments, see XMTP DEV, PRODUCTION, and LOCAL network environments. |
appContext | REQUIRED | The app context used to create and access the local database. |
dbEncryptionKey | REQUIRED | A 32-byte ByteArray used to encrypt the local database. |
historySyncUrl | https://message-history.dev.ephemera.network/ | The history sync URL used to specify where history can be synced from other devices on the network. For production apps, use https://message-history.production.ephemera.network |
appVersion | undefined | Add a client app version identifier that's included with API requests.For example, you can use the following format: appVersion: APP_NAME + '/' + APP_VERSION .Setting this value provides telemetry that shows which apps are using the XMTP client SDK. This information can help XMTP core developers provide support to app developers, especially around communicating important SDK updates, including deprecations and required updates. |
XMTP DEV, PRODUCTION, and LOCAL network environments
XMTP provides DEV
, PRODUCTION
, and LOCAL
network environments to support the development phases of your project.
The PRODUCTION
and DEV
networks are completely separate and not interchangeable.
For example, an XMTP identity on the DEV
network is completely distinct from the XMTP identity on the PRODUCTION
network, as are the messages associated with these identities. In addition, XMTP identities and messages created on the DEV
network can't be accessed from or moved to the PRODUCTION
network, and vice versa.
Here are some best practices for when to use each environment:
-
DEV
: Use to have a client communicate with theDEV
network. As a best practice, setenv
toDEV
while developing and testing your app. Follow this best practice to isolate test messages toDEV
inboxes. -
PRODUCTION
: Use to have a client communicate with thePRODUCTION
network. As a best practice, setenv
toPRODUCTION
when your app is serving real users. Follow this best practice to isolate messages between real-world users toPRODUCTION
inboxes. -
LOCAL
: Use to have a client communicate with an XMTP node you are running locally. For example, an XMTP node developer can setenv
toLOCAL
to generate client traffic to test a node running locally.
The PRODUCTION
network is configured to store messages indefinitely. XMTP may occasionally delete messages and keys from the DEV
network, and will provide advance notice in the XMTP Community Forms.
Build an existing client
Build, or resume, an existing client that's logged in and has an existing local database.
Client.build(address, {
env: "production", // 'local' | 'dev' | 'production'
dbEncryptionKey: keyBytes, // 32 bytes
});
Log out a client
When you log a user out of your app, you can give them the option to delete their local database.
await client.deleteLocalDatabase()
await Client.dropClient(client.installationId)
Check if an address is reachable
The first step to creating a conversation is to verify that participants’ addresses are reachable on XMTP. The canMessage
method checks each address’ compatibility, returning a response indicating whether each address can receive messages.
Once you have the verified addresses, you can create a new conversation, whether it's a group chat or direct message (DM).
import { Client } from "@xmtp/browser-sdk";
// response is a Map of string (address) => boolean (is reachable)
const response = await Client.canMessage([bo.address, caro.address]);
Create a conversation
Create a new group chat
Once you have the verified addresses, create a new group chat:
const group = await client.conversations.newGroup(
[bo.address, caro.address],
createGroupOptions /* optional */
);
Create a new DM
Once you have the verified addresses, create a new DM:
const group = await client.conversations.newDm(bo.address);
List conversations and messages
List new group chats or DMs
Get any new group chats or DMs from the network:
await client.conversations.sync();
List new messages
Get all new messages and conversations from the network:
await client.conversations.syncAll();
Handle unsupported content types
As more custom and standards-track content types are introduced into the XMTP ecosystem, your app may encounter content types it does not support. This situation, if not handled properly, could lead to app crashes.
Each message is accompanied by a contentFallback
property, which offers a descriptive string representing the content type's expected value. It's important to note that content fallbacks are immutable and are predefined in the content type specification. In instances where contentFallback
is undefined
, such as read receipts, it indicates that the content is not intended to be rendered. If you're venturing into creating custom content types, you're provided with the flexibility to specify a custom fallback string. For a deeper dive into this, see Build custom content types.
const codec = client.codecFor(content.contentType);
if (!codec) {
/*Not supported content type*/
if (message.contentFallback !== undefined) {
return message.contentFallback;
}
// Handle other types like ReadReceipts which are not meant to be displayed
}
List existing group chats or DMs
Get a list of existing group chats or DMs in the local database, ordered either by createdAt
date or lastMessage
.
const allConversations = await client.conversations.list();
const allGroups = await client.conversations.listGroups();
const allDms = await client.conversations.listDms();
Stream conversations and messages
Stream all group chats and DMs
Listens to the network for new group chats and DMs. Whenever a new conversation starts, it triggers the provided callback function with a ConversationContainer
object. This allows the client to immediately respond to any new group chats or DMs initiated by other users.
const stream = await client.conversations.stream();
// to stream only groups, use `client.conversations.streamGroups()`
// to stream only dms, use `client.conversations.streamDms()`
try {
for await (const conversation of stream) {
// Received a conversation
}
} catch (error) {
// log any stream errors
console.error(error);
}
Stream all group chat and DM messages
Listens to the network for new messages within all active group chats and DMs. Whenever a new message is sent to any of these conversations, the callback is triggered with a DecodedMessage
object. This keeps the inbox up to date by streaming in messages as they arrive.
// stream all messages from all conversations
const stream = await client.conversations.streamAllMessages();
// stream only group messages
const stream = await client.conversations.streamAllGroupMessages();
// stream only dm messages
const stream = await client.conversations.streamAllDmMessages();
try {
for await (const message of stream) {
// Received a message
}
} catch (error) {
// log any stream errors
console.error(error);
}
Helper methods and class interfaces
Conversation helper methods
Use these helper methods to quickly locate and access specific conversations—whether by ID, topic, group ID, or DM address—returning the appropriate ConversationContainer, group, or DM object.
// get a conversation by its ID
const conversationById = await client.conversations.getConversationById(
conversationId
);
// get a message by its ID
const messageById = await client.conversations.getMessageById(messageId);
// get a 1:1 conversation by a peer's inbox ID
const dmByInboxId = await client.conversations.getDmByInboxId(peerInboxId);
ConversationContainer interface
Serves as a unified structure for managing both group chats and DMs. It provides a consistent set of properties and methods to seamlessly handle various conversation types.
Group class
Represents a group chat conversation, providing methods to manage group-specific functionalities such as sending messages, synchronizing state, and handling group membership.
Dm class
Represents a DM conversation, providing methods to manage one-on-one communications, such as sending messages, synchronizing state, and handling message streams.