Start, list, and cache conversations with XMTP
Most of the time, when interacting with the network, you'll want to do it through conversations. Conversations are between two wallets addresses.
Check if an address is on the network
First you need to check if the address you want to message is on the XMTP network. You can do this by calling client.canMessage
with the address you want to message.
- JavaScript
- Swift
- Dart
- Kotlin
- React
- React Native
const isOnProdNetwork = await client.canMessage(
"0x3F11b27F323b62B159D2642964fa27C46C841897",
{ env: "production" },
);
let canAliceMessageBob = try await client.canMessage(bobClient.address)
val canMessage = client.canMessage(fixtures.bobClient.address)
val canMessage = client.canMessage(bobClient.address)
import { useCanMessage } from "@xmtp/react-sdk";
export const CanMessage: React.FC = () => {
const [peerAddress, setPeerAddress] = useState("");
const [isOnNetwork, setIsOnNetwork] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const { canMessage } = useCanMessage();
const handleAddressChange = useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
setPeerAddress(e.target.value);
}, []);
const handleCheckAddress = useCallback(async (e: FormEvent) => {
e.preventDefault();
if (isValidAddress(peerAddress)) {
setIsLoading(true);
setIsOnNetwork(await canMessage(peerAddress));
setIsLoading(false);
} else {
setIsOnNetwork(false);
}
};
void checkAddress();
}, [peerAddress]);
return (
<form onSubmit={handleCheckAddress}>
<input
name="addressInput"
type="text"
onChange={handleAddressChange}
disabled={isLoading}
/>
</form>
);
};
import { Client } from "@xmtp/xmtp-react-native";
const isOnDevNetwork = await Client.canMessage(
"0x3F11b27F323b62B159D2642964fa27C46C841897",
);
Be sure to provide error messaging when a user enters an address in the To field and the address hasn't yet created an XMTP identity.
Start a new conversation
You can create a new conversation with any address activated on the XMTP network. To learn more about supported addresses, see Chains.
- JavaScript
- Swift
- Dart
- Kotlin
- React
- React Native
const newConversation = await xmtp.conversations.newConversation(
"0x937C0d4a6294cdfa575de17382c7076b579DC176",
);
let newConversation = try await client.conversations.newConversation(
with: "0x3F11b27F323b62B159D2642964fa27C46C841897")
var convo = await client.newConversation("0x...");
val newConversation =
client.conversations.newConversation("0x3F11b27F323b62B159D2642964fa27C46C841897")
import { isValidAddress, useStartConversation } from "@xmtp/react-sdk";
import { useCallback, useState } from "react";
export const StartConversation: React.FC = () => {
const [peerAddress, setPeerAddress] = useState("");
const [message, setMessage] = useState("");
const [isLoading, setIsLoading] = useState(false);
const { startConversation } = useStartConversation();
const handleAddressChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
setPeerAddress(e.target.value);
},
[],
);
const handleMessageChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
setMessage(e.target.value);
},
[],
);
const handleStartConversation = useCallback(
async (e: React.FormEvent) => {
e.preventDefault();
if (peerAddress && message) {
setIsLoading(true);
const conversation = await startConversation(peerAddress, message);
setIsLoading(false);
}
},
[message, peerAddress, startConversation],
);
return (
<form onSubmit={handleStartConversation}>
<input
name="addressInput"
type="text"
onChange={handleAddressChange}
disabled={isLoading}
/>
<input
name="messageInput"
type="text"
onChange={handleMessageChange}
disabled={isLoading || !isValidAddress(peerAddress)}
/>
</form>
);
};
const newConversation = await xmtp.conversations.newConversation(
"0x3F11b27F323b62B159D2642964fa27C46C841897",
);
Need to send a test message?
Message this XMTP message bot to get an immediate automated reply:
gm.xmtp.eth
(0x937C0d4a6294cdfa575de17382c7076b579DC176
)
List existing conversations
You can get a list of all conversations that have one or more messages.
These conversations include all conversations for a user regardless of which app created the conversation. This functionality provides the concept of an interoperable inbox, which enables a user to access all of their conversations in any app built with XMTP.
To provide a user-friendly cold start (first load), display a "Loading conversations" status message and a progress bar.
- JavaScript
- Swift
- Dart
- Kotlin
- React
- React Native
const allConversations = await xmtp.conversations.list();
// Say gm to everyone you've been chatting with
for (const conversation of allConversations) {
console.log(`Saying GM to ${conversation.peerAddress}`);
await conversation.send("gm");
}
let allConversations = try await client.conversations.list()
for conversation in allConversations {
print("Saying GM to \(conversation.peerAddress)")
try await conversation.send(content: "gm")
}
var conversations = await client.listConversations();
for (var convo in conversations) {
debugPrint('Saying GM to ${convo.peer}');
await client.sendMessage(convo, 'gm');
}
val allConversations = client.conversations.list()
for (conversation in allConversations) {
print("Saying GM to ${conversation.peerAddress}")
conversation.send(text = "gm")
}
export const ListConversations: React.FC = () => {
const { conversations, error, isLoading } = useConversations();
if (error) {
return "An error occurred while loading conversations";
}
if (isLoading) {
return "Loading conversations...";
}
return (
...
);
};
const allConversations = await xmtp.conversations.list();
// Say gm to everyone you've been chatting with
for (const conversation of allConversations) {
console.log(`Saying GM to ${conversation.peerAddress}`);
await conversation.send("gm");
}
Cache the conversation list
When running in a browser, conversations are cached in LocalStorage
by default. Running client.conversations.list()
will update that cache and persist the results to the browser's LocalStorage
. The data stored in LocalStorage
is encrypted and signed using the Keystore's identity key so that attackers cannot read the sensitive contents or tamper with them. Caching the conversation list can improve performance by up to 90%.
To disable this behavior, set the persistConversations
client option to false
.
- JavaScript
- Kotlin
- React
const clientWithNoCache = await Client.create(wallet, {
persistConversations: false,
});
val client = Client().create(wallet, { env: "dev" })
val conversations = client.conversations.export()
saveConversationsSomewhere(JSON.stringify(conversations))
// To load the conversations in a new SDK instance you can run:
val client = Client.create(wallet, { env: "dev" })
val conversations = JSON.parse(loadConversationsFromSomewhere())
val client.importConversation(conversations)
const { initialize } = useClient();
const options = {
persistConversations: false,
};
await initialize({ signer, options });
Serialize/Deserialize conversations
You can save a conversation object locally using its encodedContainer
property. This returns a ConversationContainer
object which conforms to Codable
.
- Swift
- Kotlin
// Get a conversation
let conversation = try await client.conversations.newConversation(
with: "0x3F11b27F323b62B159D2642964fa27C46C841897")
// Get a container.
let container = conversation.encodedContainer
// Dump it to JSON
let encoder = JSONEncoder()
let data = try encoder.encode(container)
// Get it back from JSON
let decoder = JSONDecoder()
let containerAgain = try decoder.decode(ConversationContainer.self, from: data)
// Get an actual Conversation object like we had above
let decodedConversation = containerAgain.decode(with: client)
try await decodedConversation.send(text: "hi")
// Get a conversation
val conversation =
client.conversations.newConversation("0x3F11b27F323b62B159D2642964fa27C46C841897")
// Dump it to JSON
val gson = GsonBuilder().create()
val data = gson.toJson(conversation)
// Get it back from JSON
val containerAgain =
gson.fromJson(data.toString(StandardCharsets.UTF_8), ConversationV2Export::class.java)
// Get an actual Conversation object like we had above
val decodedConversation = containerAgain.decode(client)
decodedConversation.send(text = "hi")