Skip to content

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.

    Browser
    import 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.

    Browser
    import 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.

Browser
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:

ParameterDefaultDescription
envDEVConnect 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.
appContextREQUIREDThe app context used to create and access the local database.
dbEncryptionKeyREQUIREDA 32-byte ByteArray used to encrypt the local database.
historySyncUrlhttps://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
appVersionundefinedAdd 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 the DEV network. As a best practice, set env to DEV while developing and testing your app. Follow this best practice to isolate test messages to DEV inboxes.

  • PRODUCTION: Use to have a client communicate with the PRODUCTION network. As a best practice, set env to PRODUCTION when your app is serving real users. Follow this best practice to isolate messages between real-world users to PRODUCTION inboxes.

  • LOCAL: Use to have a client communicate with an XMTP node you are running locally. For example, an XMTP node developer can set env to LOCAL 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.

React Native
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.

React Native
  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).

Browser
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:

Browser
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:

Browser
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:

Browser
await client.conversations.sync();

List new messages

Get all new messages and conversations from the network:

Browser
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.

Browser
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.

Browser
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.

Node
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.

Node
// 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.

Browser
// 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.