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

Updated comments and readme #35

Merged
merged 3 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
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
15 changes: 14 additions & 1 deletion examples/gated-group/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,17 @@ async function checkNft(
3. Once you have the group ID, you can add members using `/add <group_id> <wallet_address>`
4. The bot will verify NFT ownership and add the wallet if they own the required NFT

The bot will automatically make the group creator a super admin and can optionally make new members admins as well.
## Run the agent

```bash
# git clone repo
git clone https://github.com/ephemeraHQ/xmtp-agent-examples.git
# go to the folder
cd xmtp-agent-examples
# install packages
yarn
# generate random keys (optional)
yarn gen:keys
# run the example
yarn examples:gated
```
22 changes: 19 additions & 3 deletions examples/gated-group/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import { Alchemy, Network } from "alchemy-sdk";
import { createSigner, getEncryptionKeyFromHex } from "@/helpers";

const settings = {
apiKey: process.env.ALCHEMY_API_KEY, // Replace with your Alchemy API key
network: Network.BASE_MAINNET, // Use the appropriate network
apiKey: process.env.ALCHEMY_API_KEY,
network: Network.BASE_MAINNET,
};

/* Get the wallet key associated to the public key of
* the agent and the encryption key for the local db
* that stores your agent's messages */
const { WALLET_KEY, ENCRYPTION_KEY } = process.env;

if (!WALLET_KEY) {
Expand All @@ -17,9 +20,11 @@ if (!ENCRYPTION_KEY) {
throw new Error("ENCRYPTION_KEY must be set");
}

/* Create the signer using viem and parse the encryption key for the local db */
const signer = createSigner(WALLET_KEY);
const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY);

/* Set the environment to dev or production */
const env: XmtpEnv = "dev";

async function main() {
Expand All @@ -39,6 +44,7 @@ async function main() {
const stream = client.conversations.streamAllMessages();

for await (const message of await stream) {
/* Ignore messages from the same agent or non-text messages */
if (
message?.senderInboxId.toLowerCase() === client.inboxId.toLowerCase() ||
message?.contentType?.typeId !== "text"
Expand All @@ -58,6 +64,11 @@ async function main() {
console.log("Unable to find conversation, skipping");
continue;
}

/* This example works by parsing slash commands to create a new group or add a member to a group
* /create - create a new group
* /add <group_id> <wallet_address> - add a member to a group */

if (message.content === "/create") {
console.log("Creating group");
const group = await client.conversations.newGroup([]);
Expand Down Expand Up @@ -97,7 +108,6 @@ async function main() {
await conversation.send("Please provide a wallet address");
return;
}

const result = await checkNft(walletAddress, "XMTPeople");
if (!result) {
console.log("User can't be added to the group");
Expand All @@ -118,6 +128,12 @@ async function main() {

main().catch(console.error);

/**
* Check if the user has the NFT
* @param walletAddress - The wallet address of the user
* @param collectionSlug - The slug of the collection
* @returns true if the user has the NFT, false otherwise
*/
async function checkNft(
walletAddress: string,
collectionSlug: string,
Expand Down
20 changes: 15 additions & 5 deletions examples/gm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ if (!ENCRYPTION_KEY) {
throw new Error("ENCRYPTION_KEY must be set");
}

/* Create the signer using viem and parse the encryption key for the local db */
const signer = createSigner(WALLET_KEY);
const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY);

Expand All @@ -31,7 +30,6 @@ const env: XmtpEnv = "dev";

async function main() {
console.log(`Creating client on the '${env}' network...`);
/* Initialize the xmtp client */
const client = await Client.create(signer, encryptionKey, { env });

console.log("Syncing conversations...");
Expand All @@ -43,7 +41,6 @@ async function main() {
);

console.log("Waiting for messages...");
/* Stream all messages from the network */
const stream = client.conversations.streamAllMessages();

for await (const message of await stream) {
Expand All @@ -59,7 +56,6 @@ async function main() {
`Received message: ${message.content as string} by ${message.senderInboxId}`,
);

/* Get the conversation by id */
const conversation = client.conversations.getConversationById(
message.conversationId,
);
Expand All @@ -70,7 +66,6 @@ async function main() {
}

console.log(`Sending "gm" response...`);
/* Send a message to the conversation */
await conversation.send("gm");

console.log("Waiting for messages...");
Expand All @@ -79,3 +74,18 @@ async function main() {

main().catch(console.error);
```

## Run the agent

```bash
# git clone repo
git clone https://github.com/ephemeraHQ/xmtp-agent-examples.git
# go to the folder
cd xmtp-agent-examples
# install packages
yarn
# generate random keys (optional)
yarn gen:keys
# run the example
yarn examples:gm
```
3 changes: 3 additions & 0 deletions examples/gm/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Client, type XmtpEnv } from "@xmtp/node-sdk";
import { createSigner, getEncryptionKeyFromHex } from "@/helpers";

/* Get the wallet key associated to the public key of
* the agent and the encryption key for the local db
* that stores your agent's messages */
const { WALLET_KEY, ENCRYPTION_KEY } = process.env;

if (!WALLET_KEY) {
Expand Down
20 changes: 18 additions & 2 deletions examples/gpt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const signer = createSigner(WALLET_KEY);
const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY);
const openai = new OpenAI({ apiKey: OPENAI_API_KEY });

/* Set the environment to dev or production */
const env: XmtpEnv = "dev";

async function main() {
Expand All @@ -55,6 +56,7 @@ async function main() {
});

console.log("Syncing conversations...");
/* Sync the conversations from the network to update the local db */
await client.conversations.sync();

console.log(
Expand All @@ -65,6 +67,7 @@ async function main() {
const stream = client.conversations.streamAllMessages();

for await (const message of await stream) {
/* Ignore messages from the same agent or non-text messages */
if (
message?.senderInboxId.toLowerCase() === client.inboxId.toLowerCase() ||
message?.contentType?.typeId !== "text"
Expand All @@ -91,9 +94,11 @@ async function main() {
model: "gpt-3.5-turbo",
});

/* Get the AI response */
const response =
completion.choices[0]?.message?.content ||
"I'm not sure how to respond to that.";

console.log(`Sending AI response: ${response}`);
await conversation.send(response);
} catch (error) {
Expand All @@ -110,6 +115,17 @@ async function main() {
main().catch(console.error);
```

Run the agent and send a test message from [xmtp.chat](https://xmtp.chat).
## Run the agent

Enjoy your GPT-powered XMTP agent!
```bash
# git clone repo
git clone https://github.com/ephemeraHQ/xmtp-agent-examples.git
# go to the folder
cd xmtp-agent-examples
# install packages
yarn
# generate random keys (optional)
yarn gen:keys
# run the example
yarn examples:gpt
```
23 changes: 23 additions & 0 deletions examples/gpt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,60 @@ import { Client, type XmtpEnv } from "@xmtp/node-sdk";
import OpenAI from "openai";
import { createSigner, getEncryptionKeyFromHex } from "@/helpers";

/* Get the wallet key associated to the public key of
* the agent and the encryption key for the local db
* that stores your agent's messages */
const { WALLET_KEY, ENCRYPTION_KEY, OPENAI_API_KEY } = process.env;

/* Check if the environment variables are set */
if (!WALLET_KEY) {
throw new Error("WALLET_KEY must be set");
}

/* Check if the encryption key is set */
if (!ENCRYPTION_KEY) {
throw new Error("ENCRYPTION_KEY must be set");
}

/* Check if the OpenAI API key is set */
if (!OPENAI_API_KEY) {
throw new Error("OPENAI_API_KEY must be set");
}

/* Create the signer using viem and parse the encryption key for the local db */
const signer = createSigner(WALLET_KEY);
const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY);

/* Initialize the OpenAI client */
const openai = new OpenAI({ apiKey: OPENAI_API_KEY });

/* Set the environment to dev or production */
const env: XmtpEnv = "dev";

/**
* Main function to run the agent
*/
async function main() {
console.log(`Creating client on the '${env}' network...`);
/* Initialize the xmtp client */
const client = await Client.create(signer, encryptionKey, {
env,
});

console.log("Syncing conversations...");
/* Sync the conversations from the network to update the local db */
await client.conversations.sync();

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

console.log("Waiting for messages...");
/* Stream all messages from the network */
const stream = client.conversations.streamAllMessages();

for await (const message of await stream) {
/* Ignore messages from the same agent or non-text messages */
if (
message?.senderInboxId.toLowerCase() === client.inboxId.toLowerCase() ||
message?.contentType?.typeId !== "text"
Expand All @@ -50,25 +67,31 @@ async function main() {
`Received message: ${message.content as string} by ${message.senderInboxId}`,
);

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

/* If the conversation is not found, skip the message */
if (!conversation) {
console.log("Unable to find conversation, skipping");
continue;
}

try {
/* Get the AI response */
const completion = await openai.chat.completions.create({
messages: [{ role: "user", content: message.content as string }],
model: "gpt-3.5-turbo",
});

/* Get the AI response */
const response =
completion.choices[0]?.message?.content ||
"I'm not sure how to respond to that.";

console.log(`Sending AI response: ${response}`);
/* Send the AI response to the conversation */
await conversation.send(response);
} catch (error) {
console.error("Error getting AI response:", error);
Expand Down
19 changes: 19 additions & 0 deletions helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ import { fromString, toString } from "uint8arrays";
import { toBytes } from "viem";
import { privateKeyToAccount } from "viem/accounts";

/**
* Create a signer from a private key
* @param privateKey - The private key of the account
* @returns The signer
*/
export const createSigner = (privateKey: `0x${string}`) => {
/* Convert the private key to an account */
const account = privateKeyToAccount(privateKey);
/* Return the signer */
return {
getAddress: () => account.address,
signMessage: async (message: string) => {
Expand All @@ -16,11 +23,23 @@ export const createSigner = (privateKey: `0x${string}`) => {
};
};

/**
* Generate a random encryption key
* @returns The encryption key
*/
export const generateEncryptionKeyHex = () => {
/* Generate a random encryption key */
const uint8Array = getRandomValues(new Uint8Array(32));
/* Convert the encryption key to a hex string */
return toString(uint8Array, "hex");
};

/**
* Get the encryption key from a hex string
* @param hex - The hex string
* @returns The encryption key
*/
export const getEncryptionKeyFromHex = (hex: string) => {
/* Convert the hex string to an encryption key */
return fromString(hex, "hex");
};