Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to node RC #44

Merged
merged 7 commits into from
Mar 13, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 41 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -26,11 +26,11 @@ ENCRYPTION_KEY= # encryption key for the local database
You can generate random keys with the following command:

```bash
yarn gen:keys
yarn gen:keys <name>
```

> [!WARNING]
> Running the `gen:keys` script will overwrite the existing `.env` file.
> [!TIP]
> Running the `gen:keys` or `gen:keys <name>` command will append keys to your existing `.env` file.

### Fetching messages

@@ -52,31 +52,51 @@ await client.conversations.sync();
await client.conversations.messages();
```

### Working with addresses
### Conversations can be of type `Group` or `Dm`

Conversations in XMTP can be `DMs` or `Groups`. The underlying technicalities are the same, but DMs are essentially groups locked between two users that can be reused - basically a fixed group of 2. This is how MLS works.

Each member of a conversation has the following properties:
The new `Group` and `Dm` classes extend the `Conversation` class and provide specific functionality based on the conversation type.

```tsx
inboxId: string; // unique identifier from the XMTP network
accountAddresses: Array<string>; // ethereum network addresses
installationIds: Array<string>; // How many active devices the user has
permissionLevel: PermissionLevel; // In the context of a group, if it's admin or not
consentState: ConsentState; // If it's blocked or allowed via consent
const conversations: (Group | Dm)[] = await client.conversations.list();

for (const conversation of conversations) {
// narrow the type to Group to access the group name
if (conversation instanceof Group) {
console.log(group.name);
}

// narrow the type to Dm to access the peer inboxId
if (conversation instanceof Dm) {
console.log(conversation.peerInboxId);
}
}
```

To fetch an ethereum address in a DM, you can use a script like the following:
## Working with addresses

Because XMTP is interoperable, you may interact with inboxes that are not on your app. In these scenarios, you will need to find the appropriate inbox ID or address.

```tsx
const address =
(await group.members?.find(
(member: any) => member.inboxId === dm.dmPeerInboxId,
)?.accountAddresses[0]) || "";
```
// get an inbox ID from an address
const inboxId = await getInboxIdForIdentifier({
identifier: "0x1234567890abcdef1234567890abcdef12345678",
identifierKind: IdentifierKind.Ethereum,
});

// find the addresses associated with an inbox ID
const inboxState = await client.inboxStateFromInboxIds([inboxId]);

interface InboxState {
inboxId: string;
recoveryIdentifier: Identifier;
installations: Installation[];
identifiers: Identifier[];
}

> [!WARNING]
> XMTP is working on integrating passkeys as a pillar of identity. Expect a breaking change soon as XMTP prepares for the first v3 stable release.
const addresses = inboxState.identifiers
.filter((i) => i.identifierKind === IdentifierKind.Ethereum)
.map((i) => i.identifier);
```

## Web inbox

@@ -97,5 +117,6 @@ Interact with the XMTP network using [xmtp.chat](https://xmtp.chat), the officia
Examples integrating XMTP with external libraries from the ecosystem

- [grok](/integrations/grok/): Integrate XMTP to the Grok API
- [gaia](/integrations/gaia/): Integrate XMTP to the Gaia API

> See all the available [integrations](/integrations/).
5 changes: 1 addition & 4 deletions examples/gated-group/README.md
Original file line number Diff line number Diff line change
@@ -20,9 +20,6 @@ You can generate random keys with the following command:
yarn gen:keys
```

> [!WARNING]
> Running the `gen:keys` script will overwrite the existing `.env` file.

## Start the XMTP agent

Start your XMTP client and begin listening to messages. The bot responds to the following commands:
@@ -33,7 +30,7 @@ Start your XMTP client and begin listening to messages. The bot responds to the
if (message.content === "/create") {
console.log("Creating group");
const group = await client.conversations.newGroup([]);
await group.addMembersByInboxId([message.senderInboxId]);
await group.addMembers([message.senderInboxId]);
await group.addSuperAdmin(message.senderInboxId);

await conversation.send(
17 changes: 10 additions & 7 deletions examples/gated-group/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Client, type XmtpEnv } from "@xmtp/node-sdk";
import { Client, type Group, type XmtpEnv } from "@xmtp/node-sdk";
import { Alchemy, Network } from "alchemy-sdk";
import { createSigner, getEncryptionKeyFromHex } from "@/helpers";

@@ -36,8 +36,11 @@ async function main() {
console.log("Syncing conversations...");
await client.conversations.sync();

const identifier = await signer.getIdentifier();
const address = identifier.identifier;

console.log(
`Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}`,
`Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}`,
);

console.log("Waiting for messages...");
@@ -56,8 +59,8 @@ async function main() {
`Received message: ${message.content as string} by ${message.senderInboxId}`,
);

const conversation = client.conversations.getConversationById(
message.conversationId,
const conversation = client.conversations.getDmByInboxId(
message.senderInboxId,
);

if (!conversation) {
@@ -74,7 +77,7 @@ async function main() {
const group = await client.conversations.newGroup([]);
console.log("Group created", group.id);
// First add the sender to the group
await group.addMembersByInboxId([message.senderInboxId]);
await group.addMembers([message.senderInboxId]);
// Then make the sender a super admin
await group.addSuperAdmin(message.senderInboxId);
console.log(
@@ -98,7 +101,7 @@ async function main() {
await conversation.send("Please provide a group id");
return;
}
const group = client.conversations.getConversationById(groupId);
const group = await client.conversations.getConversationById(groupId);
if (!group) {
await conversation.send("Please provide a valid group id");
return;
@@ -113,7 +116,7 @@ async function main() {
console.log("User can't be added to the group");
return;
} else {
await group.addMembers([walletAddress]);
await (group as Group).addMembers([walletAddress]);
await conversation.send(
`User added to the group\n- Group ID: ${groupId}\n- Wallet Address: ${walletAddress}`,
);
4 changes: 3 additions & 1 deletion examples/gm/README.md
Original file line number Diff line number Diff line change
@@ -36,8 +36,10 @@ async function main() {
/* Sync the conversations from the network to update the local db */
await client.conversations.sync();

const identifier = await signer.getIdentifier();
const address = identifier.identifier;
console.log(
`Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}?env=${env}`,
`Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}`,
);

console.log("Waiting for messages...");
18 changes: 13 additions & 5 deletions examples/gm/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Client, type XmtpEnv } from "@xmtp/node-sdk";
import { createSigner, getEncryptionKeyFromHex } from "@/helpers";
import {
createSigner,
getAddressOfMember,
getEncryptionKeyFromHex,
} from "@/helpers";

/* Get the wallet key associated to the public key of
* the agent and the encryption key for the local db
@@ -30,8 +34,10 @@ async function main() {
/* Sync the conversations from the network to update the local db */
await client.conversations.sync();

const identifier = await signer.getIdentifier();
const address = identifier.identifier;
console.log(
`Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}?env=${env}`,
`Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}`,
);

console.log("Waiting for messages...");
@@ -52,16 +58,18 @@ async function main() {
);

/* Get the conversation by id */
const conversation = client.conversations.getConversationById(
message.conversationId,
const conversation = client.conversations.getDmByInboxId(
message.senderInboxId,
);

if (!conversation) {
console.log("Unable to find conversation, skipping");
continue;
}
const members = await conversation.members();

console.log(`Sending "gm" response...`);
const address = getAddressOfMember(members, message.senderInboxId);
console.log(`Sending "gm" response to ${address}...`);
/* Send a message to the conversation */
await conversation.send("gm");

5 changes: 1 addition & 4 deletions examples/gpt/README.md
Original file line number Diff line number Diff line change
@@ -18,9 +18,6 @@ You can generate random keys with the following command:
yarn gen:keys
```

> [!WARNING]
> Running the `gen:keys` script will overwrite the existing `.env` file.

## Usage

```tsx
@@ -60,7 +57,7 @@ async function main() {
await client.conversations.sync();

console.log(
`Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}`,
`Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}`,
);

console.log("Waiting for messages...");
8 changes: 5 additions & 3 deletions examples/gpt/index.ts
Original file line number Diff line number Diff line change
@@ -46,8 +46,10 @@ async function main() {
/* Sync the conversations from the network to update the local db */
await client.conversations.sync();

const identifier = await signer.getIdentifier();
const address = identifier.identifier;
console.log(
`Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}?env=${env}`,
`Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}`,
);

console.log("Waiting for messages...");
@@ -68,8 +70,8 @@ async function main() {
);

/* Get the conversation from the local db */
const conversation = client.conversations.getConversationById(
message.conversationId,
const conversation = client.conversations.getDmByInboxId(
message.senderInboxId,
);

/* If the conversation is not found, skip the message */
40 changes: 32 additions & 8 deletions helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { getRandomValues } from "node:crypto";
import type { Signer } from "@xmtp/node-sdk";
import { IdentifierKind, type GroupMember } from "@xmtp/node-bindings";
import { type Signer } from "@xmtp/node-sdk";
import { fromString, toString } from "uint8arrays";
import { createWalletClient, http, toBytes } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { sepolia } from "viem/chains";

interface User {
key: string;
key: `0x${string}`;
account: ReturnType<typeof privateKeyToAccount>;
wallet: ReturnType<typeof createWalletClient>;
}

export const createUser = (key: string): User => {
const account = privateKeyToAccount(key as `0x${string}`);
export const createUser = (key: `0x${string}`): User => {
const accountKey = key;
const account = privateKeyToAccount(accountKey);
return {
key,
key: accountKey,
account,
wallet: createWalletClient({
account,
@@ -24,11 +26,14 @@ export const createUser = (key: string): User => {
};
};

export const createSigner = (key: string): Signer => {
export const createSigner = (key: `0x${string}`): Signer => {
const user = createUser(key);
return {
walletType: "EOA",
getAddress: () => user.account.address,
type: "EOA",
getIdentifier: () => ({
identifierKind: IdentifierKind.Ethereum,
identifier: user.account.address.toLowerCase(),
}),
signMessage: async (message: string) => {
const signature = await user.wallet.signMessage({
message,
@@ -39,6 +44,25 @@ export const createSigner = (key: string): Signer => {
};
};

/**
* Get the address of a member
* @param members - The members of the group
* @param inboxId - The inboxId of the member
* @returns The address of the member
*/
export function getAddressOfMember(members: GroupMember[], inboxId: string) {
for (const member of members) {
for (const identifier of member.accountIdentifiers) {
if (
identifier.identifierKind === IdentifierKind.Ethereum &&
member.inboxId === inboxId
) {
return identifier.identifier;
}
}
}
}

/**
* Generate a random encryption key
* @returns The encryption key
21 changes: 13 additions & 8 deletions integrations/gaia/README.md
Original file line number Diff line number Diff line change
@@ -22,17 +22,20 @@ You can generate random keys with the following command:
yarn gen:keys
```

> [!WARNING]
> Running the `gen:keys` script will overwrite the existing `.env` file.

## Usage

```tsx
import { Client, type XmtpEnv } from "@xmtp/node-sdk";
import OpenAI from "openai";
import { createSigner, getEncryptionKeyFromHex } from "@/helpers";

const { WALLET_KEY, ENCRYPTION_KEY, GAIA_NODE_URL, GAIA_API_KEY, GAIA_MODEL_NAME } = process.env;
const {
WALLET_KEY,
ENCRYPTION_KEY,
GAIA_NODE_URL,
GAIA_API_KEY,
GAIA_MODEL_NAME,
} = process.env;

if (!WALLET_KEY) {
throw new Error("WALLET_KEY must be set");
@@ -59,9 +62,9 @@ if (!GAIA_MODEL_NAME) {

const signer = createSigner(WALLET_KEY);
const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY);
const openai = new OpenAI({
baseURL: GAIA_NODE_URL,
apiKey: GAIA_API_KEY
const openai = new OpenAI({
baseURL: GAIA_NODE_URL,
apiKey: GAIA_API_KEY,
});

/* Set the environment to dev or production */
@@ -77,8 +80,10 @@ async function main() {
/* Sync the conversations from the network to update the local db */
await client.conversations.sync();

const identifier = await signer.getIdentifier();
const address = identifier.identifier;
console.log(
`Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}`,
`Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}`,
);

console.log("Waiting for messages...");
4 changes: 3 additions & 1 deletion integrations/gaia/index.ts
Original file line number Diff line number Diff line change
@@ -65,8 +65,10 @@ async function main() {
/* Sync the conversations from the network to update the local db */
await client.conversations.sync();

const identifier = await signer.getIdentifier();
const address = identifier.identifier;
console.log(
`Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}`,
`Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}`,
);

console.log("Waiting for messages...");
Loading