Repository: coinbase/agentkit
Pull request #634: Added support for XMTP chatbot example.
Author: login: raihankhan-rk
Labels: name: documentation
description: Improvements or additions to documentation
name: example
description: New example agent
name: typescript
Created: 2025-03-30T13:39:20Z | Merged: 2025-04-02T12:51:43Z

## Description
Added support for XMTP chatbot example

## Tests
![Screenshot 2025-03-30 at 7 00 29 PM](https://github.com/user-attachments/assets/d264e8ca-f54d-42cd-b102-01c2f914b39d)

```
Chatbot: Langchain XMTP Chatbot
Network: base-sepolia

Prompt: What is your balance right now?

Output: The current wallet details are as follows:

- **Wallet Address**: 0x1d58c713c83D5340F6765fab3efD9bb12d55Bb84
- **Network**: Base Sepolia Testnet
- **Native Balance**: 0 WEI (0 ETH)

Unfortunately, I was unable to check the USDC balance earlier. If you would like, I can attempt to check it again or assist you with something else. Let me know how you'd like to proceed!
```

## Checklist

A couple of things to include in your PR for completeness:

- [x] Added documentation to all relevant README.md files
- [ ] Added a changelog entry


Diff:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ae5b14d5b..e7c641bec 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -36,6 +36,7 @@ agentkit/
 │       ├── langchain-privy-chatbot/
 │       ├── langchain-solana-chatbot/
 │       ├── langchain-twitter-chatbot/
+│       ├── langchain-xmtp-chatbot/
 │       ├── model-context-protocol-smart-wallet-server/
 │       └── vercel-ai-sdk-smart-wallet-chatbot/
 ├── python/
diff --git a/README.md b/README.md
index abe258cd2..68aaed46a 100644
--- a/README.md
+++ b/README.md
@@ -150,6 +150,7 @@ agentkit/
 │       ├── langchain-privy-chatbot/
 │       ├── langchain-solana-chatbot/
 │       ├── langchain-twitter-chatbot/
+│       ├── langchain-xmtp-chatbot/
 │       ├── model-context-protocol-smart-wallet-server/
 │       └── vercel-ai-sdk-smart-wallet-chatbot/
 ├── python/
diff --git a/typescript/examples/langchain-xmtp-chatbot/.env-local b/typescript/examples/langchain-xmtp-chatbot/.env-local
new file mode 100644
index 000000000..4ecb49e1b
--- /dev/null
+++ b/typescript/examples/langchain-xmtp-chatbot/.env-local
@@ -0,0 +1,6 @@
+OPENAI_API_KEY=
+CDP_API_KEY_NAME=
+CDP_API_KEY_PRIVATE_KEY=
+WALLET_KEY=
+ENCRYPTION_KEY=
+XMTP_ENV=dev
diff --git a/typescript/examples/langchain-xmtp-chatbot/.eslintrc.json b/typescript/examples/langchain-xmtp-chatbot/.eslintrc.json
new file mode 100644
index 000000000..91571ba7a
--- /dev/null
+++ b/typescript/examples/langchain-xmtp-chatbot/.eslintrc.json
@@ -0,0 +1,4 @@
+{
+  "parser": "@typescript-eslint/parser",
+  "extends": ["../../.eslintrc.base.json"]
+}
diff --git a/typescript/examples/langchain-xmtp-chatbot/.gitignore b/typescript/examples/langchain-xmtp-chatbot/.gitignore
new file mode 100644
index 000000000..85a351506
--- /dev/null
+++ b/typescript/examples/langchain-xmtp-chatbot/.gitignore
@@ -0,0 +1,2 @@
+xmtp*.db3*
+.data
\ No newline at end of file
diff --git a/typescript/examples/langchain-xmtp-chatbot/.prettierignore b/typescript/examples/langchain-xmtp-chatbot/.prettierignore
new file mode 100644
index 000000000..20de531f4
--- /dev/null
+++ b/typescript/examples/langchain-xmtp-chatbot/.prettierignore
@@ -0,0 +1,7 @@
+docs/
+dist/
+coverage/
+.github/
+src/client
+**/**/*.json
+*.md
diff --git a/typescript/examples/langchain-xmtp-chatbot/.prettierrc b/typescript/examples/langchain-xmtp-chatbot/.prettierrc
new file mode 100644
index 000000000..ffb416b74
--- /dev/null
+++ b/typescript/examples/langchain-xmtp-chatbot/.prettierrc
@@ -0,0 +1,11 @@
+{
+  "tabWidth": 2,
+  "useTabs": false,
+  "semi": true,
+  "singleQuote": false,
+  "trailingComma": "all",
+  "bracketSpacing": true,
+  "arrowParens": "avoid",
+  "printWidth": 100,
+  "proseWrap": "never"
+}
diff --git a/typescript/examples/langchain-xmtp-chatbot/README.md b/typescript/examples/langchain-xmtp-chatbot/README.md
new file mode 100644
index 000000000..67f035dfe
--- /dev/null
+++ b/typescript/examples/langchain-xmtp-chatbot/README.md
@@ -0,0 +1,66 @@
+# CDP AgentKit LangChain XMTP Extension Examples - Chatbot Typescript
+
+This example demonstrates an agent setup on XMTP Network with access to the full set of CDP AgentKit actions.
+
+## Ask the chatbot to engage in the Web3 ecosystem!
+
+- "Transfer a portion of your ETH to a random address"
+- "What is the price of BTC?"
+- "Deploy an NFT that will go super viral!"
+- "Deploy an ERC-20 token with total supply 1 billion"
+
+## Prerequisites
+
+### Checking Node Version
+
+Before using the example, ensure that you have the correct version of Node.js installed. The example requires Node.js 18 or higher. You can check your Node version by running:
+
+```bash
+node --version
+```
+
+If you don't have the correct version, you can install it using [nvm](https://github.com/nvm-sh/nvm):
+
+```bash
+nvm install node
+```
+
+This will automatically install and use the latest version of Node.
+
+### API Keys
+
+You'll need the following API keys:
+- [CDP API Key](https://portal.cdp.coinbase.com/access/api)
+- [OpenAI API Key](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key)
+
+Once you have them, rename the `.env-local` file to `.env` and make sure you set the API keys to their corresponding environment variables:
+
+- "CDP_API_KEY_NAME"
+- "CDP_API_KEY_PRIVATE_KEY"
+- "OPENAI_API_KEY"
+- "XMTP_ENV" # defaults to "dev"
+- "WALLET_KEY" # Optional, use `npm run gen:keys` to generate a random wallet key
+- "ENCRYPTION_KEY" # Optional, use `npm run gen:keys` to generate a random encryption key
+
+## Running the example
+
+From the root directory, run:
+
+```bash
+npm install
+npm run build
+```
+
+This will install the dependencies and build the packages locally. The chatbot example uses the local `@coinbase/agentkit-langchain`, `@coinbase/agentkit`, and `@xmtp/node-sdk` packages. If you make changes to the packages, you can run `npm run build` from root again to rebuild the packages, and your changes will be reflected in the chatbot example.
+
+Now from the `typescript/examples/langchain-xmtp-chatbot` directory, run:
+
+```bash
+npm start
+```
+
+Select "1. chat mode" and start telling your Agent to do things onchain!
+
+## License
+
+Apache-2.0
diff --git a/typescript/examples/langchain-xmtp-chatbot/chatbot.ts b/typescript/examples/langchain-xmtp-chatbot/chatbot.ts
new file mode 100644
index 000000000..2dade1a45
--- /dev/null
+++ b/typescript/examples/langchain-xmtp-chatbot/chatbot.ts
@@ -0,0 +1,397 @@
+import * as dotenv from "dotenv";
+import * as fs from "fs";
+import {
+  AgentKit,
+  CdpWalletProvider,
+  walletActionProvider,
+  erc20ActionProvider,
+  cdpApiActionProvider,
+  cdpWalletActionProvider,
+} from "@coinbase/agentkit";
+import { getLangChainTools } from "@coinbase/agentkit-langchain";
+import { HumanMessage } from "@langchain/core/messages";
+import { MemorySaver } from "@langchain/langgraph";
+import { createReactAgent } from "@langchain/langgraph/prebuilt";
+import { ChatOpenAI } from "@langchain/openai";
+import {
+  Client,
+  IdentifierKind,
+  Signer,
+  // type Conversation,
+  type DecodedMessage,
+  type XmtpEnv,
+} from "@xmtp/node-sdk";
+import { fromString } from "uint8arrays";
+import { createWalletClient, http, toBytes } from "viem";
+import { privateKeyToAccount } from "viem/accounts";
+import { sepolia } from "viem/chains";
+
+// Initialize environment variables
+dotenv.config();
+
+// Storage constants
+const STORAGE_DIR = ".data/wallets";
+
+// Global stores for memory and agent instances
+const memoryStore: Record<string, MemorySaver> = {};
+const agentStore: Record<string, Agent> = {};
+
+interface AgentConfig {
+  configurable: {
+    thread_id: string;
+  };
+}
+
+type Agent = ReturnType<typeof createReactAgent>;
+
+/**
+ * Ensure local storage directory exists
+ */
+function ensureLocalStorage() {
+  if (!fs.existsSync(STORAGE_DIR)) {
+    fs.mkdirSync(STORAGE_DIR, { recursive: true });
+  }
+}
+
+/**
+ * Save wallet data to storage.
+ *
+ * @param userId - The unique identifier for the user
+ * @param walletData - The wallet data to be saved
+ */
+async function saveWalletData(userId: string, walletData: string) {
+  const localFilePath = `${STORAGE_DIR}/${userId}.json`;
+  try {
+    fs.writeFileSync(localFilePath, walletData);
+  } catch (error) {
+    console.error(`Failed to save wallet data to file: ${error}`);
+  }
+}
+
+/**
+ * Get wallet data from storage.
+ *
+ * @param userId - The unique identifier for the user
+ * @returns The wallet data as a string, or null if not found
+ */
+async function getWalletData(userId: string): Promise<string | null> {
+  const localFilePath = `${STORAGE_DIR}/${userId}.json`;
+  try {
+    if (fs.existsSync(localFilePath)) {
+      return fs.readFileSync(localFilePath, "utf8");
+    }
+  } catch (error) {
+    console.warn(`Could not read wallet data from file: ${error}`);
+  }
+  return null;
+}
+
+/**
+ * Create a viem signer from a wallet private key.
+ *
+ * @param walletKey - The private key of the wallet
+ * @returns A Signer instance for XMTP
+ */
+const createSigner = (walletKey: string): Signer => {
+  const account = privateKeyToAccount(walletKey as `0x${string}`);
+  const wallet = createWalletClient({
+    account,
+    chain: sepolia,
+    transport: http(),
+  });
+
+  return {
+    type: "EOA" as const,
+    getIdentifier: () => ({
+      identifierKind: IdentifierKind.Ethereum,
+      identifier: account.address.toLowerCase(),
+    }),
+    signMessage: async (message: string) => {
+      const signature = await wallet.signMessage({
+        message,
+        account,
+      });
+      return toBytes(signature);
+    },
+  };
+};
+
+/**
+ * Convert hex encryption key to appropriate format for XMTP.
+ *
+ * @param key - The hex string to convert
+ * @returns The key as a Uint8Array
+ */
+function getEncryptionKeyFromHex(key: string): Uint8Array {
+  const hexString = key.startsWith("0x") ? key.slice(2) : key;
+  return fromString(hexString, "hex");
+}
+
+/**
+ * Initialize the XMTP client.
+ *
+ * @returns An initialized XMTP Client instance
+ */
+async function initializeXmtpClient() {
+  const { WALLET_KEY, ENCRYPTION_KEY, XMTP_ENV } = process.env;
+
+  if (!WALLET_KEY || !ENCRYPTION_KEY || !XMTP_ENV) {
+    throw new Error("Some environment variables are not set. Please check your .env file.");
+  }
+
+  const signer = createSigner(WALLET_KEY);
+  const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY);
+  const env: XmtpEnv = XMTP_ENV as XmtpEnv;
+
+  const client = await Client.create(signer, encryptionKey, { env });
+
+  await client.conversations.sync();
+
+  const identifier = await signer.getIdentifier();
+  const address = identifier.identifier;
+
+  console.log(
+    `Agent initialized on ${env} network\nSend a message on http://xmtp.chat/dm/${address}?env=${env}`,
+  );
+
+  return client;
+}
+
+/**
+ * Initialize the agent with CDP Agentkit.
+ *
+ * @param userId - The unique identifier for the user
+ * @returns The initialized agent and its configuration
+ */
+async function initializeAgent(userId: string): Promise<{ agent: Agent; config: AgentConfig }> {
+  try {
+    if (agentStore[userId]) {
+      console.log(`Using existing agent for user: ${userId}`);
+      const agentConfig = {
+        configurable: { thread_id: userId },
+      };
+      return { agent: agentStore[userId], config: agentConfig };
+    }
+
+    const llm = new ChatOpenAI({
+      model: "gpt-4o-mini",
+    });
+
+    const storedWalletData = await getWalletData(userId);
+
+    console.log(
+      `Creating new agent for user: ${userId}, wallet data: ${storedWalletData ? "Found" : "Not found"}`,
+    );
+
+    const config = {
+      apiKeyName: process.env.CDP_API_KEY_NAME,
+      apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY?.replace(/\\n/g, "\n"),
+      cdpWalletData: storedWalletData || undefined,
+      networkId: process.env.NETWORK_ID || "base-sepolia",
+    };
+
+    const walletProvider = await CdpWalletProvider.configureWithWallet(config);
+
+    const agentkit = await AgentKit.from({
+      walletProvider,
+      actionProviders: [
+        walletActionProvider(),
+        erc20ActionProvider(),
+        cdpApiActionProvider({
+          apiKeyName: process.env.CDP_API_KEY_NAME,
+          apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY?.replace(/\\n/g, "\n"),
+        }),
+        cdpWalletActionProvider({
+          apiKeyName: process.env.CDP_API_KEY_NAME,
+          apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY?.replace(/\\n/g, "\n"),
+        }),
+      ],
+    });
+
+    const tools = await getLangChainTools(agentkit);
+
+    if (!memoryStore[userId]) {
+      console.log(`Creating new memory store for user: ${userId}`);
+      memoryStore[userId] = new MemorySaver();
+    }
+
+    const agentConfig: AgentConfig = {
+      configurable: { thread_id: userId },
+    };
+
+    const agent = createReactAgent({
+      llm,
+      tools,
+      checkpointSaver: memoryStore[userId],
+      messageModifier: `
+        You are a DeFi Payment Agent that assists users with sending payments and managing their crypto assets.
+        You can interact with the blockchain using Coinbase Developer Platform AgentKit.
+
+        When a user asks you to make a payment or check their balance:
+        1. Always check the wallet details first to see what network you're on
+        2. If on base-sepolia testnet, you can request funds from the faucet if needed
+        3. For mainnet operations, provide wallet details and request funds from the user
+
+        Your default network is Base Sepolia testnet.
+        Your main and only token for transactions is USDC. Token address is 0x036CbD53842c5426634e7929541eC2318f3dCF7e. USDC is gasless on Base.
+
+        You can only perform payment and wallet-related tasks. For other requests, politely explain that you're 
+        specialized in processing payments and can't assist with other tasks.
+                
+        If you encounter an error:
+        - For 5XX errors: Ask the user to try again later
+        - For other errors: Provide clear troubleshooting advice and offer to retry
+        
+        Be concise, helpful, and security-focused in all your interactions.
+      `,
+    });
+
+    agentStore[userId] = agent;
+
+    const exportedWallet = await walletProvider.exportWallet();
+    const walletDataJson = JSON.stringify(exportedWallet);
+    await saveWalletData(userId, walletDataJson);
+    console.log(`Wallet data saved for user ${userId}`);
+
+    return { agent, config: agentConfig };
+  } catch (error) {
+    console.error("Failed to initialize agent:", error);
+    throw error;
+  }
+}
+
+/**
+ * Process a message with the agent.
+ *
+ * @param agent - The agent instance to process the message
+ * @param config - The agent configuration
+ * @param message - The message to process
+ * @returns The processed response as a string
+ */
+async function processMessage(agent: Agent, config: AgentConfig, message: string): Promise<string> {
+  let response = "";
+
+  try {
+    const stream = await agent.stream({ messages: [new HumanMessage(message)] }, config);
+
+    for await (const chunk of stream) {
+      if ("agent" in chunk) {
+        response += chunk.agent.messages[0].content + "\n";
+      }
+    }
+
+    return response.trim();
+  } catch (error) {
+    console.error("Error processing message:", error);
+    return "Sorry, I encountered an error while processing your request. Please try again later.";
+  }
+}
+
+/**
+ * Handle incoming XMTP messages.
+ *
+ * @param message - The decoded XMTP message
+ * @param client - The XMTP client instance
+ */
+async function handleMessage(message: DecodedMessage, client: Client) {
+  try {
+    const senderAddress = message.senderInboxId;
+    const botAddress = client.inboxId.toLowerCase();
+
+    // Ignore messages from the bot itself
+    if (senderAddress.toLowerCase() === botAddress) {
+      return;
+    }
+
+    console.log(`Received message from ${senderAddress}: ${message.content}`);
+
+    const { agent, config } = await initializeAgent(senderAddress);
+    const response = await processMessage(agent, config, message.content);
+
+    // Get the conversation and send response
+    const conversation = await client.conversations.getConversationById(message.conversationId);
+    if (!conversation) {
+      throw new Error(`Could not find conversation for ID: ${message.conversationId}`);
+    }
+    await conversation.send(response);
+    console.log(`Sent response to ${senderAddress}: ${response}`);
+  } catch (error) {
+    console.error("Error handling message:", error);
+    // Send error message back to user
+    const conversation = await client.conversations.getConversationById(message.conversationId);
+    if (!conversation) {
+      console.error(`Could not find conversation for ID: ${message.conversationId}`);
+      return;
+    }
+    await conversation.send(
+      "I encountered an error while processing your request. Please try again later.",
+    );
+  }
+}
+
+/**
+ * Start listening for XMTP messages.
+ *
+ * @param client - The XMTP client instance
+ */
+async function startMessageListener(client: Client) {
+  console.log("Starting message listener...");
+  const stream = await client.conversations.streamAllMessages();
+  for await (const message of stream) {
+    if (message) {
+      await handleMessage(message, client);
+    }
+  }
+}
+
+/**
+ * Validates that required environment variables are set.
+ */
+function validateEnvironment(): void {
+  const missingVars: string[] = [];
+
+  const requiredVars = [
+    "OPENAI_API_KEY",
+    "CDP_API_KEY_NAME",
+    "CDP_API_KEY_PRIVATE_KEY",
+    "WALLET_KEY",
+    "ENCRYPTION_KEY",
+  ];
+
+  requiredVars.forEach(varName => {
+    if (!process.env[varName]) {
+      missingVars.push(varName);
+    }
+  });
+
+  if (missingVars.length > 0) {
+    console.error("Error: Required environment variables are not set");
+    missingVars.forEach(varName => {
+      console.error(`${varName}=your_${varName.toLowerCase()}_here`);
+    });
+    process.exit(1);
+  }
+
+  if (!process.env.NETWORK_ID) {
+    console.warn("Warning: NETWORK_ID not set, defaulting to base-sepolia");
+  }
+}
+
+/**
+ * Main function to start the chatbot.
+ */
+async function main(): Promise<void> {
+  console.log("Initializing Agent on XMTP...");
+
+  validateEnvironment();
+  ensureLocalStorage();
+
+  const xmtpClient = await initializeXmtpClient();
+  await startMessageListener(xmtpClient);
+}
+
+// Start the chatbot
+main().catch(error => {
+  console.error("Fatal error:", error);
+  process.exit(1);
+});
diff --git a/typescript/examples/langchain-xmtp-chatbot/package.json b/typescript/examples/langchain-xmtp-chatbot/package.json
new file mode 100644
index 000000000..186809377
--- /dev/null
+++ b/typescript/examples/langchain-xmtp-chatbot/package.json
@@ -0,0 +1,37 @@
+{
+  "name": "@coinbase/langchain-xmtp-chatbot-example",
+  "description": "Langchain Xmtp Chatbot Example",
+  "version": "1.0.0",
+  "private": true,
+  "type": "module",
+  "author": "Ephemera HQ",
+  "license": "Apache-2.0",
+  "scripts": {
+    "build": "tsc",
+    "start": "npm run build && node dist/chatbot.js",
+    "dev": "nodemon --watch '*.ts' --watch 'scripts/**/*.ts' --ignore dist/ --exec 'npm run build && node' dist/chatbot.js",
+    "gen:keys": "npm run build && node dist/scripts/generate-keys.js",
+    "lint": "eslint -c .eslintrc.json *.ts",
+    "lint:fix": "eslint -c .eslintrc.json *.ts --fix",
+    "format": "prettier --write \"**/*.{ts,js,cjs,json,md}\"",
+    "format:check": "prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\""
+  },
+  "dependencies": {
+    "@coinbase/agentkit": "workspace:*",
+    "@coinbase/agentkit-langchain": "workspace:*",
+    "@langchain/langgraph": "^0.2.21",
+    "@langchain/openai": "^0.3.14",
+    "@langchain/core": "^0.3.19",
+    "dotenv": "^16.4.5",
+    "zod": "^3.22.4",
+    "@xmtp/node-sdk": "1.0.2",
+    "uint8arrays": "^5.1.0",
+    "viem": "^2.7.15"
+  },
+  "devDependencies": {
+    "@types/node": "^22.13.14",
+    "nodemon": "^3.1.0",
+    "ts-node": "^10.9.2",
+    "typescript": "^5.3.3"
+  }
+}
diff --git a/typescript/examples/langchain-xmtp-chatbot/scripts/generate-keys.ts b/typescript/examples/langchain-xmtp-chatbot/scripts/generate-keys.ts
new file mode 100644
index 000000000..640db17cc
--- /dev/null
+++ b/typescript/examples/langchain-xmtp-chatbot/scripts/generate-keys.ts
@@ -0,0 +1,40 @@
+import * as crypto from "node:crypto";
+
+/**
+ * Generate random keys for the agent.
+ *
+ * @returns Object containing random wallet and encryption keys
+ */
+function generateRandomKeys() {
+  // This is used for development/testing only
+  const randomBytes = crypto.randomBytes(32);
+  const walletKey = `0x${randomBytes.toString("hex")}`;
+
+  const randomBytesForEncryption = crypto.randomBytes(32);
+  const encryptionKey = `0x${randomBytesForEncryption.toString("hex")}`;
+
+  return { walletKey, encryptionKey };
+}
+
+/**
+ * Main function to generate and display XMTP keys.
+ */
+async function main() {
+  try {
+    console.log("🔑 Generating random XMTP keys...\n");
+
+    // Generate random keys
+    const { walletKey, encryptionKey } = generateRandomKeys();
+
+    console.log("📋 Your XMTP keys:");
+    console.log(`WALLET_KEY=${walletKey}`);
+    console.log(`ENCRYPTION_KEY=${encryptionKey}`);
+    console.log("\n✨ Copy these keys and add them to your .env file");
+  } catch (error) {
+    console.error("❌ Error generating keys:", error);
+    process.exit(1);
+  }
+}
+
+// Call main directly since this is the entry point
+main();
diff --git a/typescript/examples/langchain-xmtp-chatbot/tsconfig.json b/typescript/examples/langchain-xmtp-chatbot/tsconfig.json
new file mode 100644
index 000000000..5ca954f24
--- /dev/null
+++ b/typescript/examples/langchain-xmtp-chatbot/tsconfig.json
@@ -0,0 +1,12 @@
+{
+  "extends": "../../tsconfig.base.json",
+  "compilerOptions": {
+    "preserveSymlinks": true,
+    "outDir": "./dist",
+    "rootDir": ".",
+    "module": "NodeNext",
+    "moduleResolution": "NodeNext",
+    "target": "ESNext"
+  },
+  "include": ["*.ts", "scripts/**/*.ts"]
+}
diff --git a/typescript/pnpm-lock.yaml b/typescript/pnpm-lock.yaml
index dc33447b6..f04347058 100644
--- a/typescript/pnpm-lock.yaml
+++ b/typescript/pnpm-lock.yaml
@@ -137,7 +137,7 @@ importers:
         version: 14.1.1
       jest:
         specifier: ^29.7.0
-        version: 29.7.0(@types/node@22.13.13)(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2))
+        version: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2))
       mock-fs:
         specifier: ^5.2.0
         version: 5.5.0
@@ -155,7 +155,7 @@ importers:
         version: 2.4.2
       ts-jest:
         specifier: ^29.2.5
-        version: 29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@22.13.13)(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2)))(typescript@5.8.2)
+        version: 29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)))(typescript@5.8.2)
       tsd:
         specifier: ^0.31.2
         version: 0.31.2
@@ -339,7 +339,7 @@ importers:
         version: 3.1.9
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@22.13.13)(typescript@5.8.2)
+        version: 10.9.2(@types/node@22.13.14)(typescript@5.8.2)
 
   examples/langchain-farcaster-chatbot:
     dependencies:
@@ -370,7 +370,7 @@ importers:
         version: 3.1.9
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@22.13.13)(typescript@5.8.2)
+        version: 10.9.2(@types/node@22.13.14)(typescript@5.8.2)
 
   examples/langchain-privy-chatbot:
     dependencies:
@@ -401,7 +401,7 @@ importers:
         version: 3.1.9
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@22.13.13)(typescript@5.8.2)
+        version: 10.9.2(@types/node@22.13.14)(typescript@5.8.2)
 
   examples/langchain-smart-wallet-chatbot:
     dependencies:
@@ -435,7 +435,7 @@ importers:
         version: 3.1.9
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@22.13.13)(typescript@5.8.2)
+        version: 10.9.2(@types/node@22.13.14)(typescript@5.8.2)
 
   examples/langchain-solana-chatbot:
     dependencies:
@@ -472,7 +472,7 @@ importers:
         version: 3.1.9
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@22.13.13)(typescript@5.8.2)
+        version: 10.9.2(@types/node@22.13.14)(typescript@5.8.2)
 
   examples/langchain-twitter-chatbot:
     dependencies:
@@ -503,7 +503,53 @@ importers:
         version: 3.1.9
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@22.13.13)(typescript@5.8.2)
+        version: 10.9.2(@types/node@22.13.14)(typescript@5.8.2)
+
+  examples/langchain-xmtp-chatbot:
+    dependencies:
+      '@coinbase/agentkit':
+        specifier: workspace:*
+        version: link:../../agentkit
+      '@coinbase/agentkit-langchain':
+        specifier: workspace:*
+        version: link:../../framework-extensions/langchain
+      '@langchain/core':
+        specifier: ^0.3.19
+        version: 0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2))
+      '@langchain/langgraph':
+        specifier: ^0.2.21
+        version: 0.2.59(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2)))(react@18.3.1)(zod-to-json-schema@3.24.5(zod@3.24.2))
+      '@langchain/openai':
+        specifier: ^0.3.14
+        version: 0.3.17(@langchain/core@0.3.30(openai@4.89.1(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.2)))(ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))
+      '@xmtp/node-sdk':
+        specifier: 1.0.2
+        version: 1.0.2
+      dotenv:
+        specifier: ^16.4.5
+        version: 16.4.7
+      uint8arrays:
+        specifier: ^5.1.0
+        version: 5.1.0
+      viem:
+        specifier: ^2.7.15
+        version: 2.24.1(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@5.0.10)(zod@3.24.2)
+      zod:
+        specifier: ^3.22.4
+        version: 3.24.2
+    devDependencies:
+      '@types/node':
+        specifier: ^22.13.14
+        version: 22.13.14
+      nodemon:
+        specifier: ^3.1.0
+        version: 3.1.9
+      ts-node:
+        specifier: ^10.9.2
+        version: 10.9.2(@types/node@22.13.14)(typescript@5.8.2)
+      typescript:
+        specifier: ^5.3.3
+        version: 5.8.2
 
   examples/model-context-protocol-smart-wallet-server:
     dependencies:
@@ -528,7 +574,7 @@ importers:
         version: 3.1.9
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@22.13.13)(typescript@5.8.2)
+        version: 10.9.2(@types/node@22.13.14)(typescript@5.8.2)
 
   examples/vercel-ai-sdk-smart-wallet-chatbot:
     dependencies:
@@ -562,7 +608,7 @@ importers:
         version: 3.1.9
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@22.13.13)(typescript@5.8.2)
+        version: 10.9.2(@types/node@22.13.14)(typescript@5.8.2)
 
   framework-extensions/langchain:
     dependencies:
@@ -1128,6 +1174,10 @@ packages:
     resolution: {integrity: sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==}
     engines: {node: '>=14'}
 
+  '@fastify/busboy@2.1.1':
+    resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
+    engines: {node: '>=14'}
+
   '@gerrit0/mini-shiki@1.27.2':
     resolution: {integrity: sha512-GeWyHz8ao2gBiUW4OJnQDxXQnFgZQwwQk05t/CVVgNBN7/rK8XZ7xY6YhLVv9tH3VppWWmr9DCl3MwemB/i+Og==}
 
@@ -1538,6 +1588,36 @@ packages:
       viem:
         optional: true
 
+  '@protobufjs/aspromise@1.1.2':
+    resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
+
+  '@protobufjs/base64@1.1.2':
+    resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==}
+
+  '@protobufjs/codegen@2.0.4':
+    resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==}
+
+  '@protobufjs/eventemitter@1.1.0':
+    resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==}
+
+  '@protobufjs/fetch@1.1.0':
+    resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==}
+
+  '@protobufjs/float@1.0.2':
+    resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==}
+
+  '@protobufjs/inquire@1.1.0':
+    resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==}
+
+  '@protobufjs/path@1.1.2':
+    resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==}
+
+  '@protobufjs/pool@1.1.0':
+    resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==}
+
+  '@protobufjs/utf8@1.1.0':
+    resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
+
   '@rtsao/scc@1.1.0':
     resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
 
@@ -1779,6 +1859,9 @@ packages:
   '@types/node@22.13.13':
     resolution: {integrity: sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==}
 
+  '@types/node@22.13.14':
+    resolution: {integrity: sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==}
+
   '@types/node@22.7.5':
     resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==}
 
@@ -2070,6 +2153,26 @@ packages:
   '@walletconnect/window-metadata@1.0.1':
     resolution: {integrity: sha512-9koTqyGrM2cqFRW517BPY/iEtUDx2r1+Pwwu5m7sJ7ka79wi3EyqhqcICk/yDmv6jAS1rjKgTKXlEhanYjijcA==}
 
+  '@xmtp/content-type-group-updated@2.0.1':
+    resolution: {integrity: sha512-ao546DrxANHAqEZxMMHm41G/sE9vtAcNCrVGaOf1x5CIMAmXmH/lTvy6JVWnNn+DeJmNr6DrkMVVfAjP95OqLA==}
+
+  '@xmtp/content-type-primitives@2.0.1':
+    resolution: {integrity: sha512-RfRwIVU2Pj2kgYxKnM+N1t1yzvt8C9lTEgma3vGqMjmh/yR3s4bGj8BhE0m8k2JCyCd0c5lQANeeaASOL1lsWQ==}
+
+  '@xmtp/content-type-text@2.0.1':
+    resolution: {integrity: sha512-cX3Swxx90O1lYi0OC+ENwPfuV/b508V7h1ZNVxt828fmpwc0LL51KZG/PREVTxwk/yiTt2f6OQqnqkC4AbL8Rg==}
+
+  '@xmtp/node-bindings@1.1.3':
+    resolution: {integrity: sha512-1n4tg39+ZQp5Vp9rpbwX4T/QMhfF3oKftQhi1DXmEaEL5T3LMMUEjJrP581e+R0z29jPp27AluSgrWvWkKbcFA==}
+    engines: {node: '>=18'}
+
+  '@xmtp/node-sdk@1.0.2':
+    resolution: {integrity: sha512-5OZMIsUT2kxJTOAjItI4j6g8VIrHO2G/5TsqSmzC+CVnskHfI2J5zQRpw/aC+E8UfUI/55kcl8I0Fh/V9nqDEQ==}
+    engines: {node: '>=20'}
+
+  '@xmtp/proto@3.78.0':
+    resolution: {integrity: sha512-JHkkvbdyqpWo1YOg88uOrc2GAsPnfXhIWaUKyIjXQdt4LltR4iAMgXcr1CFN+xjUmOBkAwwJ2JywUYv/uA4yZA==}
+
   JSONStream@1.3.5:
     resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
     hasBin: true
@@ -4170,6 +4273,9 @@ packages:
     resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==}
     engines: {node: '>=18'}
 
+  long@5.3.1:
+    resolution: {integrity: sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==}
+
   longest-streak@3.1.0:
     resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
 
@@ -4432,6 +4538,9 @@ packages:
   ms@2.1.3:
     resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
 
+  multiformats@13.3.2:
+    resolution: {integrity: sha512-qbB0CQDt3QKfiAzZ5ZYjLFOs+zW43vA4uyM8g27PeEuXZybUOFyjrVdP93HPBHMoglibwfkdVwbzfUq8qGcH6g==}
+
   multiformats@9.9.0:
     resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==}
 
@@ -4907,6 +5016,10 @@ packages:
   property-information@7.0.0:
     resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==}
 
+  protobufjs@7.4.0:
+    resolution: {integrity: sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==}
+    engines: {node: '>=12.0.0'}
+
   proxy-addr@2.0.7:
     resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
     engines: {node: '>= 0.10'}
@@ -5135,6 +5248,9 @@ packages:
   run-parallel@1.2.0:
     resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
 
+  rxjs@7.8.2:
+    resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
+
   safe-array-concat@1.1.3:
     resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
     engines: {node: '>=0.4'}
@@ -5764,6 +5880,9 @@ packages:
   uint8arrays@3.1.0:
     resolution: {integrity: sha512-ei5rfKtoRO8OyOIor2Rz5fhzjThwIHJZ3uyDPnDHTXbP0aMQ1RN/6AI5B5d9dBxJOU+BvOAk7ZQ1xphsX8Lrog==}
 
+  uint8arrays@5.1.0:
+    resolution: {integrity: sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==}
+
   unbox-primitive@1.1.0:
     resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
     engines: {node: '>= 0.4'}
@@ -5783,6 +5902,10 @@ packages:
   undici-types@6.20.0:
     resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
 
+  undici@5.29.0:
+    resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==}
+    engines: {node: '>=14.0'}
+
   unified@11.0.5:
     resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
 
@@ -6837,6 +6960,8 @@ snapshots:
       ethereum-cryptography: 2.2.1
       micro-ftch: 0.3.1
 
+  '@fastify/busboy@2.1.1': {}
+
   '@gerrit0/mini-shiki@1.27.2':
     dependencies:
       '@shikijs/engine-oniguruma': 1.29.2
@@ -6918,7 +7043,7 @@ snapshots:
       - supports-color
       - ts-node
 
-  '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2))':
+  '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2))':
     dependencies:
       '@jest/console': 29.7.0
       '@jest/reporters': 29.7.0
@@ -6932,7 +7057,7 @@ snapshots:
       exit: 0.1.2
       graceful-fs: 4.2.11
       jest-changed-files: 29.7.0
-      jest-config: 29.7.0(@types/node@20.17.27)(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2))
+      jest-config: 29.7.0(@types/node@20.17.27)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2))
       jest-haste-map: 29.7.0
       jest-message-util: 29.7.0
       jest-regex-util: 29.6.3
@@ -7582,6 +7707,29 @@ snapshots:
       - encoding
       - utf-8-validate
 
+  '@protobufjs/aspromise@1.1.2': {}
+
+  '@protobufjs/base64@1.1.2': {}
+
+  '@protobufjs/codegen@2.0.4': {}
+
+  '@protobufjs/eventemitter@1.1.0': {}
+
+  '@protobufjs/fetch@1.1.0':
+    dependencies:
+      '@protobufjs/aspromise': 1.1.2
+      '@protobufjs/inquire': 1.1.0
+
+  '@protobufjs/float@1.0.2': {}
+
+  '@protobufjs/inquire@1.1.0': {}
+
+  '@protobufjs/path@1.1.2': {}
+
+  '@protobufjs/pool@1.1.0': {}
+
+  '@protobufjs/utf8@1.1.0': {}
+
   '@rtsao/scc@1.1.0': {}
 
   '@rushstack/eslint-patch@1.11.0': {}
@@ -7915,6 +8063,10 @@ snapshots:
     dependencies:
       undici-types: 6.20.0
 
+  '@types/node@22.13.14':
+    dependencies:
+      undici-types: 6.20.0
+
   '@types/node@22.7.5':
     dependencies:
       undici-types: 6.19.8
@@ -8518,6 +8670,36 @@ snapshots:
       '@walletconnect/window-getters': 1.0.1
       tslib: 1.14.1
 
+  '@xmtp/content-type-group-updated@2.0.1':
+    dependencies:
+      '@xmtp/content-type-primitives': 2.0.1
+      '@xmtp/proto': 3.78.0
+
+  '@xmtp/content-type-primitives@2.0.1':
+    dependencies:
+      '@xmtp/proto': 3.78.0
+
+  '@xmtp/content-type-text@2.0.1':
+    dependencies:
+      '@xmtp/content-type-primitives': 2.0.1
+
+  '@xmtp/node-bindings@1.1.3': {}
+
+  '@xmtp/node-sdk@1.0.2':
+    dependencies:
+      '@xmtp/content-type-group-updated': 2.0.1
+      '@xmtp/content-type-primitives': 2.0.1
+      '@xmtp/content-type-text': 2.0.1
+      '@xmtp/node-bindings': 1.1.3
+      '@xmtp/proto': 3.78.0
+
+  '@xmtp/proto@3.78.0':
+    dependencies:
+      long: 5.3.1
+      protobufjs: 7.4.0
+      rxjs: 7.8.2
+      undici: 5.29.0
+
   JSONStream@1.3.5:
     dependencies:
       jsonparse: 1.3.1
@@ -9107,13 +9289,13 @@ snapshots:
       - supports-color
       - ts-node
 
-  create-jest@29.7.0(@types/node@22.13.13)(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2)):
+  create-jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)):
     dependencies:
       '@jest/types': 29.6.3
       chalk: 4.1.2
       exit: 0.1.2
       graceful-fs: 4.2.11
-      jest-config: 29.7.0(@types/node@22.13.13)(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2))
+      jest-config: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2))
       jest-util: 29.7.0
       prompts: 2.4.2
     transitivePeerDependencies:
@@ -10666,16 +10848,16 @@ snapshots:
       - supports-color
       - ts-node
 
-  jest-cli@29.7.0(@types/node@22.13.13)(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2)):
+  jest-cli@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)):
     dependencies:
-      '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2))
+      '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2))
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
       chalk: 4.1.2
-      create-jest: 29.7.0(@types/node@22.13.13)(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2))
+      create-jest: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2))
       exit: 0.1.2
       import-local: 3.2.0
-      jest-config: 29.7.0(@types/node@22.13.13)(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2))
+      jest-config: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2))
       jest-util: 29.7.0
       jest-validate: 29.7.0
       yargs: 17.7.2
@@ -10716,7 +10898,7 @@ snapshots:
       - babel-plugin-macros
       - supports-color
 
-  jest-config@29.7.0(@types/node@20.17.27)(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2)):
+  jest-config@29.7.0(@types/node@20.17.27)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)):
     dependencies:
       '@babel/core': 7.26.10
       '@jest/test-sequencer': 29.7.0
@@ -10742,12 +10924,12 @@ snapshots:
       strip-json-comments: 3.1.1
     optionalDependencies:
       '@types/node': 20.17.27
-      ts-node: 10.9.2(@types/node@22.13.13)(typescript@5.8.2)
+      ts-node: 10.9.2(@types/node@22.13.14)(typescript@5.8.2)
     transitivePeerDependencies:
       - babel-plugin-macros
       - supports-color
 
-  jest-config@29.7.0(@types/node@22.13.13)(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2)):
+  jest-config@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)):
     dependencies:
       '@babel/core': 7.26.10
       '@jest/test-sequencer': 29.7.0
@@ -10772,8 +10954,8 @@ snapshots:
       slash: 3.0.0
       strip-json-comments: 3.1.1
     optionalDependencies:
-      '@types/node': 22.13.13
-      ts-node: 10.9.2(@types/node@22.13.13)(typescript@5.8.2)
+      '@types/node': 22.13.14
+      ts-node: 10.9.2(@types/node@22.13.14)(typescript@5.8.2)
     transitivePeerDependencies:
       - babel-plugin-macros
       - supports-color
@@ -11005,12 +11187,12 @@ snapshots:
       - supports-color
       - ts-node
 
-  jest@29.7.0(@types/node@22.13.13)(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2)):
+  jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)):
     dependencies:
-      '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2))
+      '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2))
       '@jest/types': 29.6.3
       import-local: 3.2.0
-      jest-cli: 29.7.0(@types/node@22.13.13)(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2))
+      jest-cli: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2))
     transitivePeerDependencies:
       - '@types/node'
       - babel-plugin-macros
@@ -11190,6 +11372,8 @@ snapshots:
       chalk: 5.4.1
       is-unicode-supported: 1.3.0
 
+  long@5.3.1: {}
+
   longest-streak@3.1.0: {}
 
   loose-envify@1.4.0:
@@ -11574,6 +11758,8 @@ snapshots:
 
   ms@2.1.3: {}
 
+  multiformats@13.3.2: {}
+
   multiformats@9.9.0: {}
 
   mustache@4.2.0: {}
@@ -12092,6 +12278,21 @@ snapshots:
 
   property-information@7.0.0: {}
 
+  protobufjs@7.4.0:
+    dependencies:
+      '@protobufjs/aspromise': 1.1.2
+      '@protobufjs/base64': 1.1.2
+      '@protobufjs/codegen': 2.0.4
+      '@protobufjs/eventemitter': 1.1.0
+      '@protobufjs/fetch': 1.1.0
+      '@protobufjs/float': 1.0.2
+      '@protobufjs/inquire': 1.1.0
+      '@protobufjs/path': 1.1.2
+      '@protobufjs/pool': 1.1.0
+      '@protobufjs/utf8': 1.1.0
+      '@types/node': 20.17.27
+      long: 5.3.1
+
   proxy-addr@2.0.7:
     dependencies:
       forwarded: 0.2.0
@@ -12379,6 +12580,10 @@ snapshots:
     dependencies:
       queue-microtask: 1.2.3
 
+  rxjs@7.8.2:
+    dependencies:
+      tslib: 2.8.1
+
   safe-array-concat@1.1.3:
     dependencies:
       call-bind: 1.0.8
@@ -12920,12 +13125,12 @@ snapshots:
       '@jest/types': 29.6.3
       babel-jest: 29.7.0(@babel/core@7.26.10)
 
-  ts-jest@29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@22.13.13)(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2)))(typescript@5.8.2):
+  ts-jest@29.3.0(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2)))(typescript@5.8.2):
     dependencies:
       bs-logger: 0.2.6
       ejs: 3.1.10
       fast-json-stable-stringify: 2.1.0
-      jest: 29.7.0(@types/node@22.13.13)(ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2))
+      jest: 29.7.0(@types/node@22.13.14)(ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2))
       jest-util: 29.7.0
       json5: 2.2.3
       lodash.memoize: 4.1.2
@@ -12959,14 +13164,14 @@ snapshots:
       yn: 3.1.1
     optional: true
 
-  ts-node@10.9.2(@types/node@22.13.13)(typescript@5.8.2):
+  ts-node@10.9.2(@types/node@22.13.14)(typescript@5.8.2):
     dependencies:
       '@cspotcode/source-map-support': 0.8.1
       '@tsconfig/node10': 1.0.11
       '@tsconfig/node12': 1.0.11
       '@tsconfig/node14': 1.0.3
       '@tsconfig/node16': 1.0.4
-      '@types/node': 22.13.13
+      '@types/node': 22.13.14
       acorn: 8.14.1
       acorn-walk: 8.3.4
       arg: 4.1.3
@@ -13118,6 +13323,10 @@ snapshots:
     dependencies:
       multiformats: 9.9.0
 
+  uint8arrays@5.1.0:
+    dependencies:
+      multiformats: 13.3.2
+
   unbox-primitive@1.1.0:
     dependencies:
       call-bound: 1.0.4
@@ -13135,6 +13344,10 @@ snapshots:
 
   undici-types@6.20.0: {}
 
+  undici@5.29.0:
+    dependencies:
+      '@fastify/busboy': 2.1.1
+
   unified@11.0.5:
     dependencies:
       '@types/unist': 3.0.3


Reviews:
user: login: CarsonRoscoe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T15:24:29Z
user: login: CarsonRoscoe
body: I would recommend adding a `.gitignore` into example folder, with the following lines:

```
xmtp*.db3*
.data
```
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T15:30:38Z
user: login: raihankhan-rk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T15:39:35Z
user: login: CarsonRoscoe
body: Please add the entry into the directory maps,

`/README.md` and `/CONTRIBUTING.md` from root both list all the examples
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T15:51:30Z
user: login: CarsonRoscoe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T15:55:48Z
user: login: CarsonRoscoe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T15:57:20Z
user: login: CarsonRoscoe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T15:57:46Z
user: login: CarsonRoscoe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T15:59:33Z
user: login: CarsonRoscoe
body: Very very cool, thank you so much for this demo of integrating XMTP with ChatGPT.

I'm doing final testing, but beyond the nits I already said, I think it's good to go provided my deeper testing does not uncover anything. The readme is clear, it was easy to get running, and the code looks good. Very happy with this
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T16:01:28Z
user: login: CarsonRoscoe
body: Ah, one thing actually, our repository requires commit signing.

See here for instructions: https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification

Once this is setup, please do a rebase and re-commit your work. The commits will then be signed, and can be accepted into our repo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T16:07:10Z
user: login: humanagent
body: Can we replace in the prompt

```
Your primary token for transactions is USDC.
```

with

```
Your main and only token for transactions is USDC. Token address is 0x036CbD53842c5426634e7929541eC2318f3dCF7e. USDC is gasless on Base.
```
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T21:08:29Z
user: login: humanagent
body: XMTP storage dir is actually the SCW json storage dir.

Can we change 
```
const XMTP_STORAGE_DIR = ".data/xmtp";
```
to 
```
const STORAGE_DIR = ".data/wallets";
```
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T21:09:35Z
user: login: CarsonRoscoe
body: LGTM! Thank you for the contribution
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T23:32:09Z
user: login: CarsonRoscoe
body: The rest of the workflow actions did not run until I gave my approval.

To fix, follow these steps:
1. `pnpm install` from `typescript` folder to generated the lock file
2. `pnpm format` from `typescript` folder to re-format the files 
3. `pnpm lint:fix` from `typescript` folder to try and fix any linter errors. It will print any it could not resolve on its own, and you'll have to manually fix. Usually it's just jsdoc's it needs manual intervention with.

I suspect after those three steps, the workflows will all pass. I am happy to merge once they all pass :)
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T23:36:32Z
user: login: CarsonRoscoe
body: @raihankhan-rk just had to re-approve the workflows to run after your latest commit.

Passed now, LGTM!

Thank you again for the exciting demo, very cool
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T12:51:18Z

Review comments:
diff_hunk: @@ -0,0 +1,66 @@
+# CDP AgentKit LangChain XMTP Extension Examples - Chatbot Typescript
+
+This example demonstrates an agent setup on XMTP Network with access to the full set of CDP AgentKit actions.
+
+## Ask the chatbot to engage in the Web3 ecosystem!
+
+- "Transfer a portion of your ETH to a random address"
+- "What is the price of BTC?"
+- "Deploy an NFT that will go super viral!"
+- "Deploy an ERC-20 token with total supply 1 billion"
+
+## Prerequisites
+
+### Checking Node Version
+
+Before using the example, ensure that you have the correct version of Node.js installed. The example requires Node.js 18 or higher. You can check your Node version by running:
+
+```bash
+node --version
+```
+
+If you don't have the correct version, you can install it using [nvm](https://github.com/nvm-sh/nvm):
+
+```bash
+nvm install node
+```
+
+This will automatically install and use the latest version of Node.
+
+### API Keys
+
+You'll need the following API keys:
+- [CDP API Key](https://portal.cdp.coinbase.com/access/api)
+- [OpenAI API Key](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key)
+
+Once you have them, rename the `.env-local` file to `.env` and make sure you set the API keys to their corresponding environment variables:
+
+- "CDP_API_KEY_NAME"
+- "CDP_API_KEY_PRIVATE_KEY"
+- "OPENAI_API_KEY"
+- "XMTP_ENV" # defaults to "dev"
+- "WALLET_KEY" # Optional, use `yarn gen:keys` to generate a random wallet key
path: typescript/examples/langchain-xmtp-chatbot/README.md
user: login: CarsonRoscoe
body: Plese replace `yarn gen:keys` with `npm run gen:keys`
created_at: 2025-04-01T15:24:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+# CDP AgentKit LangChain XMTP Extension Examples - Chatbot Typescript
+
+This example demonstrates an agent setup on XMTP Network with access to the full set of CDP AgentKit actions.
+
+## Ask the chatbot to engage in the Web3 ecosystem!
+
+- "Transfer a portion of your ETH to a random address"
+- "What is the price of BTC?"
+- "Deploy an NFT that will go super viral!"
+- "Deploy an ERC-20 token with total supply 1 billion"
+
+## Prerequisites
+
+### Checking Node Version
+
+Before using the example, ensure that you have the correct version of Node.js installed. The example requires Node.js 18 or higher. You can check your Node version by running:
+
+```bash
+node --version
+```
+
+If you don't have the correct version, you can install it using [nvm](https://github.com/nvm-sh/nvm):
+
+```bash
+nvm install node
+```
+
+This will automatically install and use the latest version of Node.
+
+### API Keys
+
+You'll need the following API keys:
+- [CDP API Key](https://portal.cdp.coinbase.com/access/api)
+- [OpenAI API Key](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key)
+
+Once you have them, rename the `.env-local` file to `.env` and make sure you set the API keys to their corresponding environment variables:
+
+- "CDP_API_KEY_NAME"
+- "CDP_API_KEY_PRIVATE_KEY"
+- "OPENAI_API_KEY"
+- "XMTP_ENV" # defaults to "dev"
+- "WALLET_KEY" # Optional, use `yarn gen:keys` to generate a random wallet key
path: typescript/examples/langchain-xmtp-chatbot/README.md
user: login: raihankhan-rk
body: @CarsonRoscoe done!
created_at: 2025-04-01T15:39:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,400 @@
+import * as dotenv from "dotenv";
+import * as fs from "fs";
+import {
+  AgentKit,
+  CdpWalletProvider,
+  walletActionProvider,
+  erc20ActionProvider,
+  cdpApiActionProvider,
+  cdpWalletActionProvider,
+} from "@coinbase/agentkit";
+import { getLangChainTools } from "@coinbase/agentkit-langchain";
+import { HumanMessage } from "@langchain/core/messages";
+import { MemorySaver } from "@langchain/langgraph";
+import { createReactAgent } from "@langchain/langgraph/prebuilt";
+import { ChatOpenAI } from "@langchain/openai";
+import {
+  Client,
+  IdentifierKind,
+  Signer,
+  // type Conversation,
+  type DecodedMessage,
+  type XmtpEnv,
+} from "@xmtp/node-sdk";
+import { fromString } from "uint8arrays";
+import { createWalletClient, http, toBytes } from "viem";
+import { privateKeyToAccount } from "viem/accounts";
+import { sepolia } from "viem/chains";
+
+// Initialize environment variables
+dotenv.config();
+
+// Storage constants
+const XMTP_STORAGE_DIR = ".data/xmtp";
+
+// Global stores for memory and agent instances
+const memoryStore: Record<string, MemorySaver> = {};
+const agentStore: Record<string, Agent> = {};
+
+interface AgentConfig {
+  configurable: {
+    thread_id: string;
+  };
+}
+
+type Agent = ReturnType<typeof createReactAgent>;
+
+/**
+ * Ensure local storage directory exists
+ */
+function ensureLocalStorage() {
+  if (!fs.existsSync(XMTP_STORAGE_DIR)) {
+    fs.mkdirSync(XMTP_STORAGE_DIR, { recursive: true });
+  }
+}
+
+/**
+ * Save wallet data to storage.
+ *
+ * @param userId - The unique identifier for the user
+ * @param walletData - The wallet data to be saved
+ */
+async function saveWalletData(userId: string, walletData: string) {
+  const localFilePath = `${XMTP_STORAGE_DIR}/${userId}.json`;
+  try {
+    fs.writeFileSync(localFilePath, walletData);
+  } catch (error) {
+    console.error(`Failed to save wallet data to file: ${error}`);
+  }
+}
+
+/**
+ * Get wallet data from storage.
+ *
+ * @param userId - The unique identifier for the user
+ * @returns The wallet data as a string, or null if not found
+ */
+async function getWalletData(userId: string): Promise<string | null> {
+  const localFilePath = `${XMTP_STORAGE_DIR}/${userId}.json`;
+  try {
+    if (fs.existsSync(localFilePath)) {
+      return fs.readFileSync(localFilePath, "utf8");
+    }
+  } catch (error) {
+    console.warn(`Could not read wallet data from file: ${error}`);
+  }
+  return null;
+}
+
+/**
+ * Create a viem signer from a wallet private key.
+ *
+ * @param walletKey - The private key of the wallet
+ * @returns A Signer instance for XMTP
+ */
+const createSigner = (walletKey: string): Signer => {
+  const account = privateKeyToAccount(walletKey as `0x${string}`);
+  const wallet = createWalletClient({
+    account,
+    chain: sepolia,
+    transport: http(),
+  });
+
+  return {
+    type: "EOA" as const,
+    getIdentifier: () => ({
+      identifierKind: IdentifierKind.Ethereum,
+      identifier: account.address.toLowerCase(),
+    }),
+    signMessage: async (message: string) => {
+      const signature = await wallet.signMessage({
+        message,
+        account,
+      });
+      return toBytes(signature);
+    },
+  };
+};
+
+/**
+ * Convert hex encryption key to appropriate format for XMTP.
+ *
+ * @param key - The hex string to convert
+ * @returns The key as a Uint8Array
+ */
+function getEncryptionKeyFromHex(key: string): Uint8Array {
+  const hexString = key.startsWith("0x") ? key.slice(2) : key;
+  return fromString(hexString, "hex");
+}
+
+/**
+ * Initialize the XMTP client.
+ *
+ * @returns An initialized XMTP Client instance
+ */
+async function initializeXmtpClient() {
+  const { WALLET_KEY, ENCRYPTION_KEY, XMTP_ENV } = process.env;
+
+  if (!WALLET_KEY || !ENCRYPTION_KEY || !XMTP_ENV) {
+    throw new Error("Some environment variables are not set. Please check your .env file.");
+  }
+
+  const signer = createSigner(WALLET_KEY);
+  const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY);
+  const env: XmtpEnv = XMTP_ENV as XmtpEnv;
+
+  console.log(`Creating XMTP client on the '${env}' network...`);
+  const client = await Client.create(signer, encryptionKey, { env });
+
+  console.log("Syncing conversations...");
path: typescript/examples/langchain-xmtp-chatbot/chatbot.ts
user: login: CarsonRoscoe
body: In general, we try to minimize logs in the examples. When there's too many logs, it can be hard to follow along. However, I think this being such a unique example, that it would be helpful for builders to have the meaningful logs.

That said, I think a few of these can be removed. Like `Syncing conversations...`
created_at: 2025-04-01T15:55:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,400 @@
+import * as dotenv from "dotenv";
+import * as fs from "fs";
+import {
+  AgentKit,
+  CdpWalletProvider,
+  walletActionProvider,
+  erc20ActionProvider,
+  cdpApiActionProvider,
+  cdpWalletActionProvider,
+} from "@coinbase/agentkit";
+import { getLangChainTools } from "@coinbase/agentkit-langchain";
+import { HumanMessage } from "@langchain/core/messages";
+import { MemorySaver } from "@langchain/langgraph";
+import { createReactAgent } from "@langchain/langgraph/prebuilt";
+import { ChatOpenAI } from "@langchain/openai";
+import {
+  Client,
+  IdentifierKind,
+  Signer,
+  // type Conversation,
+  type DecodedMessage,
+  type XmtpEnv,
+} from "@xmtp/node-sdk";
+import { fromString } from "uint8arrays";
+import { createWalletClient, http, toBytes } from "viem";
+import { privateKeyToAccount } from "viem/accounts";
+import { sepolia } from "viem/chains";
+
+// Initialize environment variables
+dotenv.config();
+
+// Storage constants
+const XMTP_STORAGE_DIR = ".data/xmtp";
+
+// Global stores for memory and agent instances
+const memoryStore: Record<string, MemorySaver> = {};
+const agentStore: Record<string, Agent> = {};
+
+interface AgentConfig {
+  configurable: {
+    thread_id: string;
+  };
+}
+
+type Agent = ReturnType<typeof createReactAgent>;
+
+/**
+ * Ensure local storage directory exists
+ */
+function ensureLocalStorage() {
+  if (!fs.existsSync(XMTP_STORAGE_DIR)) {
+    fs.mkdirSync(XMTP_STORAGE_DIR, { recursive: true });
+  }
+}
+
+/**
+ * Save wallet data to storage.
+ *
+ * @param userId - The unique identifier for the user
+ * @param walletData - The wallet data to be saved
+ */
+async function saveWalletData(userId: string, walletData: string) {
+  const localFilePath = `${XMTP_STORAGE_DIR}/${userId}.json`;
+  try {
+    fs.writeFileSync(localFilePath, walletData);
+  } catch (error) {
+    console.error(`Failed to save wallet data to file: ${error}`);
+  }
+}
+
+/**
+ * Get wallet data from storage.
+ *
+ * @param userId - The unique identifier for the user
+ * @returns The wallet data as a string, or null if not found
+ */
+async function getWalletData(userId: string): Promise<string | null> {
+  const localFilePath = `${XMTP_STORAGE_DIR}/${userId}.json`;
+  try {
+    if (fs.existsSync(localFilePath)) {
+      return fs.readFileSync(localFilePath, "utf8");
+    }
+  } catch (error) {
+    console.warn(`Could not read wallet data from file: ${error}`);
+  }
+  return null;
+}
+
+/**
+ * Create a viem signer from a wallet private key.
+ *
+ * @param walletKey - The private key of the wallet
+ * @returns A Signer instance for XMTP
+ */
+const createSigner = (walletKey: string): Signer => {
+  const account = privateKeyToAccount(walletKey as `0x${string}`);
+  const wallet = createWalletClient({
+    account,
+    chain: sepolia,
+    transport: http(),
+  });
+
+  return {
+    type: "EOA" as const,
+    getIdentifier: () => ({
+      identifierKind: IdentifierKind.Ethereum,
+      identifier: account.address.toLowerCase(),
+    }),
+    signMessage: async (message: string) => {
+      const signature = await wallet.signMessage({
+        message,
+        account,
+      });
+      return toBytes(signature);
+    },
+  };
+};
+
+/**
+ * Convert hex encryption key to appropriate format for XMTP.
+ *
+ * @param key - The hex string to convert
+ * @returns The key as a Uint8Array
+ */
+function getEncryptionKeyFromHex(key: string): Uint8Array {
+  const hexString = key.startsWith("0x") ? key.slice(2) : key;
+  return fromString(hexString, "hex");
+}
+
+/**
+ * Initialize the XMTP client.
+ *
+ * @returns An initialized XMTP Client instance
+ */
+async function initializeXmtpClient() {
+  const { WALLET_KEY, ENCRYPTION_KEY, XMTP_ENV } = process.env;
+
+  if (!WALLET_KEY || !ENCRYPTION_KEY || !XMTP_ENV) {
+    throw new Error("Some environment variables are not set. Please check your .env file.");
+  }
+
+  const signer = createSigner(WALLET_KEY);
+  const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY);
+  const env: XmtpEnv = XMTP_ENV as XmtpEnv;
+
+  console.log(`Creating XMTP client on the '${env}' network...`);
+  const client = await Client.create(signer, encryptionKey, { env });
+
+  console.log("Syncing conversations...");
+  await client.conversations.sync();
+
+  const identifier = await signer.getIdentifier();
+  const address = identifier.identifier;
+
+  console.log(
+    `Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}?env=${env}`,
+  );
+
+  return client;
+}
+
+/**
+ * Initialize the agent with CDP Agentkit.
+ *
+ * @param userId - The unique identifier for the user
+ * @returns The initialized agent and its configuration
+ */
+async function initializeAgent(userId: string): Promise<{ agent: Agent; config: AgentConfig }> {
+  try {
+    if (agentStore[userId]) {
+      console.log(`Using existing agent for user: ${userId}`);
+      const agentConfig = {
+        configurable: { thread_id: userId },
+      };
+      return { agent: agentStore[userId], config: agentConfig };
+    }
+
+    console.log(`Creating new agent for user: ${userId}`);
+
+    const llm = new ChatOpenAI({
+      model: "gpt-4o-mini",
+    });
+
+    const storedWalletData = await getWalletData(userId);
+    console.log(`Wallet data for ${userId}: ${storedWalletData ? "Found" : "Not found"}`);
path: typescript/examples/langchain-xmtp-chatbot/chatbot.ts
user: login: CarsonRoscoe
body: Can we merge this log with the one above it?

```
console.log(`Creating new agent for user: ${userId} (Wallet data: ${storedWalletData ? "Found" : "Not found"})`);
```
created_at: 2025-04-01T15:57:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,400 @@
+import * as dotenv from "dotenv";
+import * as fs from "fs";
+import {
+  AgentKit,
+  CdpWalletProvider,
+  walletActionProvider,
+  erc20ActionProvider,
+  cdpApiActionProvider,
+  cdpWalletActionProvider,
+} from "@coinbase/agentkit";
+import { getLangChainTools } from "@coinbase/agentkit-langchain";
+import { HumanMessage } from "@langchain/core/messages";
+import { MemorySaver } from "@langchain/langgraph";
+import { createReactAgent } from "@langchain/langgraph/prebuilt";
+import { ChatOpenAI } from "@langchain/openai";
+import {
+  Client,
+  IdentifierKind,
+  Signer,
+  // type Conversation,
+  type DecodedMessage,
+  type XmtpEnv,
+} from "@xmtp/node-sdk";
+import { fromString } from "uint8arrays";
+import { createWalletClient, http, toBytes } from "viem";
+import { privateKeyToAccount } from "viem/accounts";
+import { sepolia } from "viem/chains";
+
+// Initialize environment variables
+dotenv.config();
+
+// Storage constants
+const XMTP_STORAGE_DIR = ".data/xmtp";
+
+// Global stores for memory and agent instances
+const memoryStore: Record<string, MemorySaver> = {};
+const agentStore: Record<string, Agent> = {};
+
+interface AgentConfig {
+  configurable: {
+    thread_id: string;
+  };
+}
+
+type Agent = ReturnType<typeof createReactAgent>;
+
+/**
+ * Ensure local storage directory exists
+ */
+function ensureLocalStorage() {
+  if (!fs.existsSync(XMTP_STORAGE_DIR)) {
+    fs.mkdirSync(XMTP_STORAGE_DIR, { recursive: true });
+  }
+}
+
+/**
+ * Save wallet data to storage.
+ *
+ * @param userId - The unique identifier for the user
+ * @param walletData - The wallet data to be saved
+ */
+async function saveWalletData(userId: string, walletData: string) {
+  const localFilePath = `${XMTP_STORAGE_DIR}/${userId}.json`;
+  try {
+    fs.writeFileSync(localFilePath, walletData);
+  } catch (error) {
+    console.error(`Failed to save wallet data to file: ${error}`);
+  }
+}
+
+/**
+ * Get wallet data from storage.
+ *
+ * @param userId - The unique identifier for the user
+ * @returns The wallet data as a string, or null if not found
+ */
+async function getWalletData(userId: string): Promise<string | null> {
+  const localFilePath = `${XMTP_STORAGE_DIR}/${userId}.json`;
+  try {
+    if (fs.existsSync(localFilePath)) {
+      return fs.readFileSync(localFilePath, "utf8");
+    }
+  } catch (error) {
+    console.warn(`Could not read wallet data from file: ${error}`);
+  }
+  return null;
+}
+
+/**
+ * Create a viem signer from a wallet private key.
+ *
+ * @param walletKey - The private key of the wallet
+ * @returns A Signer instance for XMTP
+ */
+const createSigner = (walletKey: string): Signer => {
+  const account = privateKeyToAccount(walletKey as `0x${string}`);
+  const wallet = createWalletClient({
+    account,
+    chain: sepolia,
+    transport: http(),
+  });
+
+  return {
+    type: "EOA" as const,
+    getIdentifier: () => ({
+      identifierKind: IdentifierKind.Ethereum,
+      identifier: account.address.toLowerCase(),
+    }),
+    signMessage: async (message: string) => {
+      const signature = await wallet.signMessage({
+        message,
+        account,
+      });
+      return toBytes(signature);
+    },
+  };
+};
+
+/**
+ * Convert hex encryption key to appropriate format for XMTP.
+ *
+ * @param key - The hex string to convert
+ * @returns The key as a Uint8Array
+ */
+function getEncryptionKeyFromHex(key: string): Uint8Array {
+  const hexString = key.startsWith("0x") ? key.slice(2) : key;
+  return fromString(hexString, "hex");
+}
+
+/**
+ * Initialize the XMTP client.
+ *
+ * @returns An initialized XMTP Client instance
+ */
+async function initializeXmtpClient() {
+  const { WALLET_KEY, ENCRYPTION_KEY, XMTP_ENV } = process.env;
+
+  if (!WALLET_KEY || !ENCRYPTION_KEY || !XMTP_ENV) {
+    throw new Error("Some environment variables are not set. Please check your .env file.");
+  }
+
+  const signer = createSigner(WALLET_KEY);
+  const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY);
+  const env: XmtpEnv = XMTP_ENV as XmtpEnv;
+
+  console.log(`Creating XMTP client on the '${env}' network...`);
+  const client = await Client.create(signer, encryptionKey, { env });
+
+  console.log("Syncing conversations...");
+  await client.conversations.sync();
+
+  const identifier = await signer.getIdentifier();
+  const address = identifier.identifier;
+
+  console.log(
+    `Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}?env=${env}`,
+  );
+
+  return client;
+}
+
+/**
+ * Initialize the agent with CDP Agentkit.
+ *
+ * @param userId - The unique identifier for the user
+ * @returns The initialized agent and its configuration
+ */
+async function initializeAgent(userId: string): Promise<{ agent: Agent; config: AgentConfig }> {
+  try {
+    if (agentStore[userId]) {
+      console.log(`Using existing agent for user: ${userId}`);
+      const agentConfig = {
+        configurable: { thread_id: userId },
+      };
+      return { agent: agentStore[userId], config: agentConfig };
+    }
+
+    console.log(`Creating new agent for user: ${userId}`);
+
+    const llm = new ChatOpenAI({
+      model: "gpt-4o-mini",
+    });
+
+    const storedWalletData = await getWalletData(userId);
+    console.log(`Wallet data for ${userId}: ${storedWalletData ? "Found" : "Not found"}`);
+
+    const config = {
+      apiKeyName: process.env.CDP_API_KEY_NAME,
+      apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY?.replace(/\\n/g, "\n"),
+      cdpWalletData: storedWalletData || undefined,
+      networkId: process.env.NETWORK_ID || "base-sepolia",
+    };
+
+    const walletProvider = await CdpWalletProvider.configureWithWallet(config);
+
+    const agentkit = await AgentKit.from({
+      walletProvider,
+      actionProviders: [
+        walletActionProvider(),
+        erc20ActionProvider(),
+        cdpApiActionProvider({
+          apiKeyName: process.env.CDP_API_KEY_NAME,
+          apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY?.replace(/\\n/g, "\n"),
+        }),
+        cdpWalletActionProvider({
+          apiKeyName: process.env.CDP_API_KEY_NAME,
+          apiKeyPrivateKey: process.env.CDP_API_KEY_PRIVATE_KEY?.replace(/\\n/g, "\n"),
+        }),
+      ],
+    });
+
+    const tools = await getLangChainTools(agentkit);
+
+    if (!memoryStore[userId]) {
+      console.log(`Creating new memory store for user: ${userId}`);
+      memoryStore[userId] = new MemorySaver();
+    } else {
+      console.log(`Using existing memory store for user: ${userId}`);
path: typescript/examples/langchain-xmtp-chatbot/chatbot.ts
user: login: CarsonRoscoe
body: I don't think we need to log the existing memory case, i would remove the else
created_at: 2025-04-01T15:57:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -37,6 +37,7 @@ agentkit/
 │       ├── langchain-solana-chatbot/
 │       ├── langchain-twitter-chatbot/
 │       ├── model-context-protocol-smart-wallet-server/
+│       ├── langchain-xmtp-chatbot/
path: CONTRIBUTING.md
user: login: CarsonRoscoe
body: Apologies, meant to mention these lists are in alphabetical order. Please put the line above `model-context-protocol-smart-wallet-server`
created_at: 2025-04-01T15:59:32Z
author_association: CONTRIBUTOR

PR comments:
user: login: cb-heimdall
created_at: 2025-03-30T13:39:28Z
body: <h1>✅ Heimdall Review Status</h1>
<table>
<tr>
<th>Requirement</th>
<th>Status</th>
<th>More Info</th>
</tr>
<tr>
<td>Reviews</td>
<td>
✅
<code>1/1</code>
</td>
<td>
<details>
<summary>Denominator calculation</summary>
<table>
<tr>
<td>
<details>
<summary>Show calculation</summary>
<table>
<tr>
<td><code>1</code> if user is bot</td>
<td>0</td>
</tr>
<tr>
<td><code>1</code> if user is external</td>
<td>0</td>
</tr>
<tr>
<td>
From <code>.codeflow.yml</code>
</td>
<td>1</td>
</tr>
<tr>
<td>
Additional review requirements
<details>
<summary>Show calculation</summary>
<table>
<tr>
<td><strong>Max</strong></td>
<td><strong>0</strong></td>
</tr>
</table>
</details>
</td>
<td>0</td>
</tr>
<tr>
<td>
From <code>CODEOWNERS</code>
</td>
<td>0</td>
</tr>
<tr>
<td>Global minimum</td>
<td>0</td>
</tr>
<tr>
<td><strong>Max</strong></td>
<td>
<strong>
1
</strong>
</td>
</tr>
</table>
</details>
</td>
<td>1</td>
</tr>
<tr>
<td><code>1</code> if commit is unverified</td>
<td>0</td>
</tr>
<tr>
<td><strong>Sum</strong></td>
<td><strong>1</strong></td>
</tr>
</table>
</details>
</td>
</tr>
</table>
<!-- CODEOWNERS & Bar Raisers -->
<!-- Power Rangers -->
<!-- Links -->

author_association: NONE
user: login: raihankhan-rk
created_at: 2025-04-01T15:40:12Z
body: @CarsonRoscoe readme updated and gitignore added as well. Lmk in case anything else required
author_association: CONTRIBUTOR
user: login: raihankhan-rk
created_at: 2025-04-01T15:54:45Z
body: @CarsonRoscoe done! Please check once again.
author_association: CONTRIBUTOR
user: login: raihankhan-rk
created_at: 2025-04-01T22:25:06Z
body: @CarsonRoscoe @humanagent all requested changes are done. Please review and merge if all ok!
author_association: CONTRIBUTOR
user: login: raihankhan-rk
created_at: 2025-04-02T06:12:31Z
body: @CarsonRoscoe pnpm install, format, lint done. Although nothing changed. Everything ran successfully but you may still have a look
author_association: CONTRIBUTOR