Skip to content

Build a chat inbox

Create or build a client

Create an account SigningKey

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.

    React Native
    // Example EOA
    export function convertEOAToSigner(eoaAccount: EOAAccount): Signer {
      return {
        getAddress: async () => eoaAccount.address,
        signMessage: async (message: string | Uint8Array) =>
          eoaAccount.signMessage({
            message: typeof message === "string" ? message : { raw: message },
          }),
        walletType: () => undefined, // Default: 'EOA'
        getChainId: () => undefined,
        getBlockNumber: () => undefined,
      };
    }
  • 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.

    React Native
    // Example SCW
    export function convertSCWToSigner(scwAccount: SCWAccount): Signer {
      return {
        getAddress: async () => scwAccount.address,
        signMessage: async (message: string) => {
          const byteArray = await scwAccount.signMessage(message);
          return ethers.utils.hexlify(byteArray); // Convert to hex string
        },
        walletType: () => "SCW",
        getChainId: async () => 8453, // https://chainlist.org/
        getBlockNumber: async () => undefined, // Optional: will be computed at run
      };
    }

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.

Node
import { Client } from "@xmtp/node-sdk";
 
const client = await Client.create(alix.address, options /* optional */);

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 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
});

Check if an address is reachable

The first step to creating a conversation is to verify that participants’ addresses are reachable on XMTP. The canGroupMessage 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).

Node
import { Client } from "@xmtp/node-sdk";
 
const client = await Client.create(alix.address);
// 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:

Node
import { Client } from "@xmtp/node-sdk";
 
const client = await Client.create(alix.address);
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:

Node
import { Client } from "@xmtp/node-sdk";
 
const client = await Client.create(alix.address, options /* optional */);
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:

Node
import { Client } from "@xmtp/node-sdk";
 
const client = await Client.create(alix.address);
await client.conversations.sync();

List new messages

Get new messages from the network for all existing group chats and DMs in the local database:

Node
import { Client } from "@xmtp/node-sdk";
 
const client = await Client.create(alix.address);
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.

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

Node
import { Client } from "@xmtp/node-sdk";
 
const client = await Client.create(alix.address);
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
import { Client } from "@xmtp/node-sdk";
 
const client = await Client.create(alix.address);
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
import { Client } from "@xmtp/node-sdk";
 
const client = await Client.create(alix.address);
const stream = await client.conversations.streamAllMessages();
// to stream only group messages, use `client.conversations.streamAllGroupMessages()`
// to stream only dm messages, use `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.

Node
import { Client } from "@xmtp/node-sdk";
 
const client = await Client.create(alix.address);
 
// 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.