Skip to content

Commit 3c69e97

Browse files
committed
Merge upstream main branch
2 parents af56c14 + 1c1b942 commit 3c69e97

28 files changed

+657
-2113
lines changed

.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
WALLET_KEY= # the private key of the wallet
22
ENCRYPTION_KEY= # a second random 32 bytes encryption key for local db encryption
3+
XMTP_ENV=dev # the environment to use, dev, local or production
34

45
# GPT agent example
56
OPENAI_API_KEY= # the API key for the OpenAI

README.md

+62-22
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,38 @@ This repository contains examples of agents that use the [XMTP](https://docs.xmt
1818

1919
To run your XMTP agent, you must create a `.env` file with the following variables:
2020

21-
```bash
21+
```tsx
2222
WALLET_KEY= # the private key of the wallet
2323
ENCRYPTION_KEY= # encryption key for the local database
2424
```
2525

2626
You can generate random keys with the following command:
2727

28-
```bash
29-
yarn gen:keys
28+
```tsx
29+
yarn gen:keys <name>
3030
```
3131

3232
> [!WARNING]
33-
> Running the `gen:keys` script will overwrite the existing `.env` file.
33+
> Running the `gen:keys` or `gen:keys <name>` command will append keys to your existing `.env` file.
34+
35+
### Work in local network
36+
37+
`Dev` and `production` networks are hosted by XMTP, while `local` network is hosted by yourself. Use local network for development purposes only.
38+
39+
- 1. Install docker
40+
- 2. Start the XMTP service and database
41+
42+
```tsx
43+
./dev/up
44+
```
45+
46+
- 3. Change the .env file to use the local network
47+
48+
```tsx
49+
XMTP_ENV = local;
50+
```
51+
52+
## Concepts
3453

3554
### Fetching messages
3655

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

55-
### Working with addresses
56-
57-
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.
74+
### Conversations can be of type `Group` or `Dm`
5875

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

6178
```tsx
62-
inboxId: string; // unique identifier from the XMTP network
63-
accountAddresses: Array<string>; // ethereum network addresses
64-
installationIds: Array<string>; // How many active devices the user has
65-
permissionLevel: PermissionLevel; // In the context of a group, if it's admin or not
66-
consentState: ConsentState; // If it's blocked or allowed via consent
79+
const conversations: (Group | Dm)[] = await client.conversations.list();
80+
81+
for (const conversation of conversations) {
82+
// narrow the type to Group to access the group name
83+
if (conversation instanceof Group) {
84+
console.log(group.name);
85+
}
86+
87+
// narrow the type to Dm to access the peer inboxId
88+
if (conversation instanceof Dm) {
89+
console.log(conversation.peerInboxId);
90+
}
91+
}
6792
```
6893

69-
To fetch an ethereum address in a DM, you can use a script like the following:
94+
### Working with addresses
95+
96+
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.
7097

7198
```tsx
72-
const address =
73-
(await group.members?.find(
74-
(member: any) => member.inboxId === dm.dmPeerInboxId,
75-
)?.accountAddresses[0]) || "";
76-
```
99+
// get an inbox ID from an address
100+
const inboxId = await getInboxIdForIdentifier({
101+
identifier: "0x1234567890abcdef1234567890abcdef12345678",
102+
identifierKind: IdentifierKind.Ethereum,
103+
});
104+
105+
// find the addresses associated with an inbox ID
106+
const inboxState = await client.inboxStateFromInboxIds([inboxId]);
107+
108+
interface InboxState {
109+
inboxId: string;
110+
recoveryIdentifier: Identifier;
111+
installations: Installation[];
112+
identifiers: Identifier[];
113+
}
77114

78-
> [!WARNING]
79-
> 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.
115+
const addresses = inboxState.identifiers
116+
.filter((i) => i.identifierKind === IdentifierKind.Ethereum)
117+
.map((i) => i.identifier);
118+
```
80119

81120
## Web inbox
82121

@@ -96,6 +135,7 @@ Interact with the XMTP network using [xmtp.chat](https://xmtp.chat), the officia
96135

97136
Examples integrating XMTP with external libraries from the ecosystem
98137

99-
- [grok](/integrations/grok/): Integrate XMTP to the Grok API
138+
- [grok](/integrations/grok/): Integrate your agent with the Grok API
139+
- [gaia](/integrations/gaia/): Integrate with the Gaia API
100140

101141
> See all the available [integrations](/integrations/).

dev/compose

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
set -eou pipefail
3+
4+
docker compose -f dev/docker-compose.yml -p "xmtp-js" "$@"

dev/docker-compose.yml

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
services:
2+
node:
3+
image: xmtp/node-go:latest
4+
platform: linux/amd64
5+
environment:
6+
- GOWAKU-NODEKEY=8a30dcb604b0b53627a5adc054dbf434b446628d4bd1eccc681d223f0550ce67
7+
command:
8+
- --store.enable
9+
- --store.db-connection-string=postgres://postgres:xmtp@db:5432/postgres?sslmode=disable
10+
- --store.reader-db-connection-string=postgres://postgres:xmtp@db:5432/postgres?sslmode=disable
11+
- --mls-store.db-connection-string=postgres://postgres:xmtp@mlsdb:5432/postgres?sslmode=disable
12+
- --mls-validation.grpc-address=validation:50051
13+
- --api.enable-mls
14+
- --wait-for-db=30s
15+
- --api.authn.enable
16+
ports:
17+
- 5555:5555
18+
- 5556:5556
19+
depends_on:
20+
- db
21+
- mlsdb
22+
- validation
23+
24+
validation:
25+
image: ghcr.io/xmtp/mls-validation-service:main
26+
platform: linux/amd64
27+
28+
db:
29+
image: postgres:13
30+
environment:
31+
POSTGRES_PASSWORD: xmtp
32+
33+
mlsdb:
34+
image: postgres:13
35+
environment:
36+
POSTGRES_PASSWORD: xmtp
37+
38+
upload-service:
39+
build: ./uploadService
40+
ports:
41+
- 3000:3000

dev/down

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
set -eou pipefail
3+
script_dir="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
4+
5+
"${script_dir}"/compose down

dev/up

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash
2+
set -eou pipefail
3+
script_dir="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
4+
5+
"${script_dir}"/compose pull
6+
"${script_dir}"/compose up -d --wait

dev/uploadService/Dockerfile

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Fetching the minified node image on apline linux
2+
FROM node:18.19-slim
3+
4+
WORKDIR /uploadService
5+
COPY . .
6+
7+
RUN apt-get update && \
8+
apt-get install -y openssl && \
9+
openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -sha256 -days 3650 -subj /CN=localhost -out cert.pem
10+
11+
RUN npm install
12+
13+
CMD ["node", "index.js"]
14+
15+
EXPOSE 3000

dev/uploadService/index.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const fs = require("fs");
2+
const https = require("https");
3+
const express = require("express");
4+
const bodyParser = require("body-parser");
5+
const app = express();
6+
const port = 3000;
7+
8+
const key = fs.readFileSync("key.pem", "utf-8");
9+
const cert = fs.readFileSync("cert.pem", "utf-8");
10+
11+
const UPLOADS = {};
12+
13+
app.use(bodyParser.raw({ type: "application/octet-stream" }));
14+
15+
app.get("/:path", (req, res) => {
16+
const path = req.params.path;
17+
console.log(`GET /${path}`);
18+
const file = UPLOADS[path];
19+
if (file) {
20+
res.header("Content-Type", "application/octet-stream");
21+
res.send(file);
22+
} else {
23+
console.log(`Upload path found: ${path}`);
24+
}
25+
});
26+
27+
app.post("/:path", (req, res) => {
28+
const path = req.params.path;
29+
console.log(`POST /${path}`);
30+
UPLOADS[path] = req.body;
31+
res.sendStatus(200);
32+
});
33+
34+
https.createServer({ key, cert }, app).listen(port, () => {
35+
console.log(`Upload service listening on port ${port}`);
36+
});

dev/uploadService/package.json

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "uploadservice",
3+
"version": "0.0.0",
4+
"private": true,
5+
"main": "index.js",
6+
"scripts": {
7+
"start": "node index.js"
8+
},
9+
"dependencies": {
10+
"body-parser": "^1.20.2",
11+
"express": "^4.18.2"
12+
}
13+
}

examples/gated-group/README.md

+1-4
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ You can generate random keys with the following command:
2020
yarn gen:keys
2121
```
2222

23-
> [!WARNING]
24-
> Running the `gen:keys` script will overwrite the existing `.env` file.
25-
2623
## Start the XMTP agent
2724

2825
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
3330
if (message.content === "/create") {
3431
console.log("Creating group");
3532
const group = await client.conversations.newGroup([]);
36-
await group.addMembersByInboxId([message.senderInboxId]);
33+
await group.addMembers([message.senderInboxId]);
3734
await group.addSuperAdmin(message.senderInboxId);
3835

3936
await conversation.send(

examples/gated-group/index.ts

+12-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Client, type XmtpEnv } from "@xmtp/node-sdk";
1+
import { Client, type Group, type XmtpEnv } from "@xmtp/node-sdk";
22
import { Alchemy, Network } from "alchemy-sdk";
33
import { createSigner, getEncryptionKeyFromHex } from "@/helpers";
44

@@ -24,8 +24,8 @@ if (!ENCRYPTION_KEY) {
2424
const signer = createSigner(WALLET_KEY);
2525
const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY);
2626

27-
/* Set the environment to dev or production */
28-
const env: XmtpEnv = "dev";
27+
/* Set the environment to local, dev or production */
28+
const env: XmtpEnv = process.env.XMTP_ENV as XmtpEnv;
2929

3030
async function main() {
3131
console.log(`Creating client on the '${env}' network...`);
@@ -36,8 +36,11 @@ async function main() {
3636
console.log("Syncing conversations...");
3737
await client.conversations.sync();
3838

39+
const identifier = await signer.getIdentifier();
40+
const address = identifier.identifier;
41+
3942
console.log(
40-
`Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}`,
43+
`Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}?env=${env}`,
4144
);
4245

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

59-
const conversation = client.conversations.getConversationById(
60-
message.conversationId,
62+
const conversation = client.conversations.getDmByInboxId(
63+
message.senderInboxId,
6164
);
6265

6366
if (!conversation) {
@@ -74,7 +77,7 @@ async function main() {
7477
const group = await client.conversations.newGroup([]);
7578
console.log("Group created", group.id);
7679
// First add the sender to the group
77-
await group.addMembersByInboxId([message.senderInboxId]);
80+
await group.addMembers([message.senderInboxId]);
7881
// Then make the sender a super admin
7982
await group.addSuperAdmin(message.senderInboxId);
8083
console.log(
@@ -98,7 +101,7 @@ async function main() {
98101
await conversation.send("Please provide a group id");
99102
return;
100103
}
101-
const group = client.conversations.getConversationById(groupId);
104+
const group = await client.conversations.getConversationById(groupId);
102105
if (!group) {
103106
await conversation.send("Please provide a valid group id");
104107
return;
@@ -113,7 +116,7 @@ async function main() {
113116
console.log("User can't be added to the group");
114117
return;
115118
} else {
116-
await group.addMembers([walletAddress]);
119+
await (group as Group).addMembers([walletAddress]);
117120
await conversation.send(
118121
`User added to the group\n- Group ID: ${groupId}\n- Wallet Address: ${walletAddress}`,
119122
);

examples/gm/README.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ if (!ENCRYPTION_KEY) {
2525
const signer = createSigner(WALLET_KEY);
2626
const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY);
2727

28-
/* Set the environment to dev or production */
29-
const env: XmtpEnv = "dev";
28+
/* Set the environment to local, dev or production */
29+
const env: XmtpEnv = process.env.XMTP_ENV as XmtpEnv;
3030

3131
async function main() {
3232
console.log(`Creating client on the '${env}' network...`);
@@ -36,8 +36,10 @@ async function main() {
3636
/* Sync the conversations from the network to update the local db */
3737
await client.conversations.sync();
3838

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

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

0 commit comments

Comments
 (0)