Build authentication with XMTP
The XMTP message API revolves around a network client that allows retrieving and sending messages to other network participants. A client must be connected to a wallet on startup. The client will request a wallet signature in two cases:
- To sign the newly generated key bundle. This happens only the very first time when key bundle is not found in storage.
- To sign a random salt used to encrypt the key bundle in storage. This happens every time the client is started (including the very first time).
Create a client
A client is created that requires passing in a connected wallet that implements the Signer interface. Use client configuration options to change parameters of a client's network connection.
- JavaScript
- React
- Kotlin
- Swift
- Dart
- React Native
import { Client } from "@xmtp/xmtp-js";
// Create the client with a `Signer` from your application
const xmtp = await Client.create(signer, { env: "dev" });
To use the hooks provided by the React SDK, you must wrap your app with an XMTPProvider
. This gives the hooks access to the XMTP client instance.
import { XMTPProvider } from "@xmtp/react-sdk";
createRoot(document.getElementById("root") as HTMLElement).render(
<StrictMode>
<XMTPProvider>
<App />
</XMTPProvider>
</StrictMode>,
);
export const CreateClient: React.FC<{ signer: Signer }> = ({ signer }) => {
const { client, error, isLoading, initialize } = useClient();
const handleConnect = useCallback(async () => {
const options = {
persistConversations: false,
env: "dev",
};
await initialize({ keys, options, signer });
}, [initialize]);
if (error) {
return "An error occurred while initializing the client";
}
if (isLoading) {
return "Awaiting signatures...";
}
if (!client) {
return (
<button type="button" onClick={handleConnect}>
Connect to XMTP
</button>
);
}
return "Connected to XMTP";
};
// Create the client with a `SigningKey` from your app
val options =
ClientOptions(
api = ClientOptions.Api(env = XMTPEnvironment.PRODUCTION, isSecure = true)
)
val client = Client().create(account = account, options = options)
import XMTP
// Create the client with a `SigningKey` from your app
let client = try await Client.create(
account: account, options: .init(api: .init(env: .production)))
var api = xmtp.Api.create();
var client = await Client.createFromWallet(api, signer);
import { Client } from "@xmtp/xmtp-react-native";
// Create the client with a `Signer` from your application
const xmtp = await Client.create(signer);
Example:
Saving keys
You can export the unencrypted key bundle using the static method getKeys
, save it somewhere secure, and then provide those keys at a later time to initialize a new client using the exported XMTP identity.
- JavaScript
- React
- Kotlin
- Swift
- Dart
- React Native
// Get the keys using a valid Signer. Save them somewhere secure.
import { loadKeys, storeKeys } from "./helpers";
const clientOptions = {
env: "production",
};
let keys = loadKeys(address);
if (!keys) {
keys = await Client.getKeys(signer, {
...clientOptions,
// we don't need to publish the contact here since it
// will happen when we create the client later
skipContactPublishing: true,
// we can skip persistence on the keystore for this short-lived
// instance
persistConversations: false,
});
storeKeys(address, keys);
}
const client = await Client.create(null, {
...clientOptions,
privateKeyOverride: keys,
});
We are using the following helper funtions to store and retrieve the keys
// Create a client using keys returned from getKeys
const ENCODING = "binary";
export const getEnv = (): "dev" | "production" | "local" => {
return "production";
};
export const buildLocalStorageKey = (walletAddress: string) =>
walletAddress ? `xmtp:${getEnv()}:keys:${walletAddress}` : "";
export const loadKeys = (walletAddress: string): Uint8Array | null => {
const val = localStorage.getItem(buildLocalStorageKey(walletAddress));
return val ? Buffer.from(val, ENCODING) : null;
};
export const storeKeys = (walletAddress: string, keys: Uint8Array) => {
localStorage.setItem(
buildLocalStorageKey(walletAddress),
Buffer.from(keys).toString(ENCODING),
);
};
export const wipeKeys = (walletAddress: string) => {
// This will clear the conversation cache + the private keys
localStorage.removeItem(buildLocalStorageKey(walletAddress));
};
import { Client, useClient } from "@xmtp/react-sdk";
import type { Signer } from "@xmtp/react-sdk";
export const CreateClientWithKeys: React.FC<{ signer: Signer }> = ({ signer }) => {
const { initialize } = useClient();
const initXmtpWithKeys = async () => {
const options = {
env: "dev"
};
const address = await getAddress(signer);
let keys = loadKeys(address);
if (!keys) {
keys = await Client.getKeys(signer, {
...options,
skipContactPublishing: true,
persistConversations: false,
});
storeKeys(address, keys);
}
await initialize({ keys, options, signer });
};
// initialize client on mount
useEffect(() => {
void initXmtpWithKeys();
}, []);
return (
...
);
};
We are using the following helper funtions to store and retrieve the keys
// Create a client using keys returned from getKeys
const ENCODING = "binary";
export const getEnv = (): "dev" | "production" | "local" => {
return "production";
};
export const buildLocalStorageKey = (walletAddress: string) =>
walletAddress ? `xmtp:${getEnv()}:keys:${walletAddress}` : "";
export const loadKeys = (walletAddress: string): Uint8Array | null => {
const val = localStorage.getItem(buildLocalStorageKey(walletAddress));
return val ? Buffer.from(val, ENCODING) : null;
};
export const storeKeys = (walletAddress: string, keys: Uint8Array) => {
localStorage.setItem(
buildLocalStorageKey(walletAddress),
Buffer.from(keys).toString(ENCODING),
);
};
export const wipeKeys = (walletAddress: string) => {
// This will clear the conversation cache + the private keys
localStorage.removeItem(buildLocalStorageKey(walletAddress));
};
// Create the client with a `SigningKey` from your app
val options =
ClientOptions(
api = ClientOptions.Api(env = XMTPEnvironment.PRODUCTION, isSecure = true)
)
val client = Client().create(account = account, options = options)
// Get the key bundle
val keys = client.privateKeyBundleV1
// Serialize the key bundle and store it somewhere safe
val serializedKeys = PrivateKeyBundleV1Builder.encodeData(v1)
Once you have those keys, you can create a new client with Client().buildFrom()
:
val keys = PrivateKeyBundleV1Builder.fromEncodedData(serializedKeys)
val client = Client().buildFrom(bundle = keys, options = options)
// Create the client with a `SigningKey` from your app
let client = try await Client.create(
account: account, options: .init(api: .init(env: .production)))
// Get the key bundle
let keys = client.privateKeyBundle
// Serialize the key bundle and store it somewhere safe
let keysData = try keys.serializedData()
Once you have those keys, you can create a new client with Client.from
:
let keys = try PrivateKeyBundle(serializedData: keysData)
let client = try Client.from(bundle: keys, options: .init(api: .init(env: .production)))
var api = xmtp.Api.create(host: 'dev.xmtp.network', isSecure: true)
var client = await Client.createFromWallet(api, signer);
await mySecureStorage.save(client.keys.writeToBuffer());
//The second time a user launches the app they should call `createFromKeys` using the stored `keys` from their previous session.
var stored = await mySecureStorage.load();
var keys = xmtp.PrivateKeyBundle.fromBuffer(stored);
var api = xmtp.Api.create();
var client = await Client.createFromKeys(api, keys);
You can export the unencrypted key bundle using the static method Client.exportKeyBundle
, save it somewhere secure, and then provide those keys at a later time to initialize a new client using the exported XMTP identity.
import { Client } from "@xmtp/xmtp-react-native";
// Get the keys using a valid Signer. Save them somewhere secure.
const keys = await Client.exportKeyBundle(xmtp.address);
//Save the keys
storeKeyBundle(xmtp.address, keys);
//Load the keys
let keys = await loadKeyBundle(address);
// Create a client using keys returned from getKeys
const xmtp = await Client.createFromKeyBundle(keys, clientOptions);
In this example we are using AsyncStorage
external library to save the key bundle
export const buildLocalStorageKey = (walletAddress) => {
return walletAddress ? `xmtp:${getEnv()}:keys:${walletAddress}` : "";
};
export const loadKeyBundle = async (address) => {
const keyBundle = await AsyncStorage.getItem(buildLocalStorageKey(address));
return keyBundle;
};
export const storeKeyBundle = async (address, keyBundle) => {
await AsyncStorage.setItem(buildLocalStorageKey(address), keyBundle);
};
export const wipeKeyBundle = async (address) => {
await AsyncStorage.removeItem(buildLocalStorageKey(address));
};
The keys returned by exportKeyBundle
should be treated with the utmost care as compromise of these keys will allow an attacker to impersonate the user on the XMTP network. Ensure these keys are stored somewhere secure and encrypted.
Start from private key
You can create an XMTP client with a private key using a compatible client library.
- JavaScript
- React
- Kotlin
- Swift
- Dart
- React Native
import { Client } from "@xmtp/xmtp-js";
const privateKey = "your_private_key";
//ethers
import { Wallet } from "ethers";
const signer = new Wallet();
//viem
import { privateKeyToAccount } from "viem/accounts";
const hexPrivateKey = `0x${privateKey}`;
const signer = privateKeyToAccount(hexPrivateKey);
// Create the client with a `Signer` from your application
const xmtp = await Client.create(signer, { env: "dev" });
import { useClient } from "@xmtp/react-sdk";
const privateKey = "your_private_key";
//ethers
import { Wallet } from "ethers";
const signer = new Wallet(privateKey);
//viem
import { privateKeyToAccount } from "viem/accounts";
const hexPrivateKey = `0x${privateKey}`;
const signer = privateKeyToAccount(hexPrivateKey);
//Using react hooks
const { client, error, isLoading, initialize } = useClient();
await initialize({ keys, options, signer });
// Create the client with a `SigningKey` from your app
val options =
ClientOptions(
api = ClientOptions.Api(env = XMTPEnvironment.PRODUCTION, isSecure = true)
)
val client = Client().create(account = account, options = options)
func dataFromHexString(_ hex: String) -> Data? {
var data = Data(capacity: hex.count / 2)
var buffer = 0
var index = 0
for char in hex {
if let value = char.hexDigitValue {
if index % 2 == 0 {
buffer = value << 4
} else {
buffer |= value
data.append(UInt8(buffer))
}
index += 1
} else {
return nil
}
}
return data
}
let privateKeyString = "your_private_key"
if let privateKeyData = dataFromHexString(privateKeyString) {
let privateKey = try PrivateKey(privateKeyData)
let client = try await Client.create(account: privateKey, options: ClientOptions(api: .init(env: .production)))
import 'package:web3dart/web3dart.dart';
import 'package:xmtp/xmtp.dart' as xmtp;
var signer = EthPrivateKey.fromHex('your_private_key').asSigner();
print('Wallet address: ${await signer.address}');
var api = xmtp.Api.create(host: 'dev.xmtp.network', isSecure: true);
var client = await xmtp.Client.createFromWallet(api, signer);
import { Client } from "@xmtp/xmtp-react-native";
import { Wallet } from "ethers";
const privateKey = "your_key";
const provider = new ethers.InfuraProvider("mainnet", "your_infura_token");
const signer = new ethers.Wallet(privateKey, provider);
const xmtp = await Client.create(signer);
Configure the client
Configure a client's network connection and other options using these client creation parameters:
Set the env
client option to dev
while developing. Set it to production
before you launch.
- JavaScript
- React
- Kotlin
- Swift
- Dart
- React Native
Parameter | Default | Description |
---|---|---|
appVersion | undefined | Add a client app version identifier that's included with API requests. Production apps are strongly encouraged to set this value. 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 developers provide app support, especially around communicating important SDK updates, including deprecations and required upgrades. |
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 production and dev network environments. |
apiUrl | undefined | Manually specify an API URL to use. If specified, value of env will be ignored. |
keystoreProviders | [StaticKeystoreProvider, NetworkKeystoreProvider, KeyGeneratorKeystoreProvider] | Override the default behavior of how the Client creates a Keystore with a custom provider. This can be used to get the user's private keys from a different storage mechanism. |
persistConversations | true | Maintain a cache of previously seen V2 conversations in the storage provider (defaults to LocalStorage ). |
skipContactPublishing | false | Do not publish the user's contact bundle to the network on Client creation. Designed to be used in cases where the Client session is short-lived (for example, decrypting a push notification), and where it is known that a Client instance has been instantiated with this flag set to false at some point in the past. |
codecs | [TextCodec] | Add codecs to support additional content types. |
maxContentSize | 100M | Maximum message content size in bytes. |
preCreateIdentityCallback | undefined | preCreateIdentityCallback is a function that will be called immediately before a Create Identity wallet signature is requested from the user. |
preEnableIdentityCallback | undefined | preEnableIdentityCallback is a function that will be called immediately before an Enable Identity wallet signature is requested from the user. |
useSnaps | false | Enabling the useSnaps flag will allow the client to attempt to connect to the "Sign in with XMTP" MetaMask Snap as part of client creation. It is safe to enable this flag even if you do not know whether the user has an appropriate MetaMask version enabled. If no compatible version of MetaMask is found, client creation will proceed as if this flag was set to false . To learn more, see Build with the Snap. |
basePersistence | InMemoryPersistence (Node.js) or LocalStoragePersistence (browser) | A persistence provider used by the Keystore to persist its cache of conversations and metadata. Ignored in cases where the useSnaps is enabled and the user has a Snaps-compatible browser. To learn more, see Keystore and Pluggable persistence. |
apiClientFactory | HttpApiClient | Override the function used to create an API client for the XMTP network. If you are running xmtp-js on a server, you will want to import @xmtp/grpc-api-client and set this option to GrpcApiClient.fromOptions for better performance and reliability. To learn more, see Pluggable gRPC API client. |
Parameter | Default | Description |
---|---|---|
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 developers provide app support, especially around communicating important SDK updates, including deprecations and required upgrades. |
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 production and dev network environments. |
apiUrl | undefined | Manually specify an API URL to use. If specified, value of env will be ignored. |
keystoreProviders | [StaticKeystoreProvider, NetworkKeystoreProvider, KeyGeneratorKeystoreProvider] | Override the default behavior of how the client creates a Keystore with a custom provider. This can be used to get the user's private keys from a different storage mechanism. |
persistConversations | true | Maintain a cache of previously seen V2 conversations in the storage provider (defaults to LocalStorage). |
skipContactPublishing | false | Do not publish the user's contact bundle to the network on client creation. Designed to be used in cases where the client session is short-lived (for example, decrypting a push notification), and where it is known that a client instance has been instantiated with this flag set to false at some point in the past. |
codecs | [TextCodec] | Add codecs to support additional content types. |
maxContentSize | 100M | Maximum message content size in bytes. |
preCreateIdentityCallback | undefined | preCreateIdentityCallback is a function that will be called immediately before a Create Identity wallet signature is requested from the user. |
preEnableIdentityCallback | undefined | preEnableIdentityCallback is a function that will be called immediately before an Enable Identity wallet signature is requested from the user. |
Parameter | Default | Description |
---|---|---|
appVersion | undefined | Add a client app version identifier that's included with API requests. Production apps are strongly encouraged to set this value. 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 developers provide app support, especially around communicating important SDK updates, including deprecations and required upgrades. |
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 production and dev network environments. |
Configure env
// Configure the client to use the `production` network
val options =
ClientOptions(
api = ClientOptions.Api(env = XMTPEnvironment.PRODUCTION, isSecure = true)
)
val client = Client().create(account = account, options = options)
The apiUrl
, keyStoreType
, codecs
, and maxContentSize
parameters from the XMTP client SDK for JavaScript (xmtp-js) are not yet supported.
Parameter | Default | Description |
---|---|---|
appVersion | undefined | Add a client app version identifier that's included with API requests. Production apps are strongly encouraged to set this value. 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 developers provide app support, especially around communicating important SDK updates, including deprecations and required upgrades. |
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 production and dev network environments. |
Configure env
// Configure the client to use the `production` network
let clientOptions = ClientOptions(api: .init(env: .production))
let client = try await Client.create(account: account, options: clientOptions)
You can configure the client environment when you call Api.create()
. By default, it will connect to a local
XMTP network.
xmtp.Api.create(host: '127.0.0.1', port: 5556, isSecure: false)
xmtp.Api.create(host: 'dev.xmtp.network', isSecure: true)
xmtp.Api.create(host: 'production.xmtp.network', isSecure: true)*/
Parameter | Default | Description |
---|---|---|
appVersion | undefined | Add a client app version identifier that's included with API requests. Production apps are strongly encouraged to set this value. 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 developers provide app support, especially around communicating important SDK updates, including deprecations and required upgrades. |
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 production and dev network environments. |
Environments
XMTP identity on dev
network is completely distinct from its XMTP identity on the production
network, as are the messages associated with these identities.
production
: This network is used in production and is configured to store messages indefinitely.Try the web inbox at https://xmtp.chat/.
Send a message to our
gm
bot to get started.gm.xmtp.eth
dev
: XMTP may occasionally delete messages and keys from this networkTry the web dev inbox at https://dev.xmtp.chat/.
Send a message to our
gm
bot to get started.0x8DC925338C1eE1fE62c0C43404371deb701BfB55
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.