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
Show file tree
Hide file tree
Changes from 6 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
Expand Up @@ -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 write to the existing `.env` file.

### Fetching messages

Expand All @@ -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

Expand All @@ -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
Expand Up @@ -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:
Expand All @@ -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(
Expand Down
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";

Expand Down Expand Up @@ -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...");
Expand All @@ -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) {
Expand All @@ -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(
Expand All @@ -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;
Expand All @@ -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}`,
);
Expand Down
4 changes: 3 additions & 1 deletion examples/gm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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...");
Expand Down
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
Expand Down Expand Up @@ -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...");
Expand All @@ -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");

Expand Down
5 changes: 1 addition & 4 deletions examples/gpt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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...");
Expand Down
8 changes: 5 additions & 3 deletions examples/gpt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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...");
Expand All @@ -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 */
Expand Down
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,
Expand All @@ -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,
Expand All @@ -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
Expand Down
21 changes: 13 additions & 8 deletions integrations/gaia/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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 */
Expand All @@ -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...");
Expand Down
4 changes: 3 additions & 1 deletion integrations/gaia/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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...");
Expand Down
Loading