<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>nerderlyne</title>
        <link>https://paragraph.com/@nerderlyne</link>
        <description>undefined</description>
        <lastBuildDate>Wed, 08 Apr 2026 05:14:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Build ChatGPT for XMTP]]></title>
            <link>https://paragraph.com/@nerderlyne/build-chatgpt-for-xmtp</link>
            <guid>QzzmaqyW6WSdblEjBxIe</guid>
            <pubDate>Sun, 24 Sep 2023 21:12:07 GMT</pubDate>
            <description><![CDATA[In this tutorial, we&apos;ll create an LLM powered chatbot for XMTP (Extensible Message Transport Protocol). XMTP is a secure, private web3 messaging...]]></description>
            <content:encoded><![CDATA[<p>In this tutorial, we&apos;ll create an LLM powered chatbot for <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://xmtp.org/">XMTP (Extensible Message Transport Protocol)</a>. XMTP is a secure, private web3 messaging protocol—combined with language models, you can build an intelligent and secure chatbot that anyone, anywhere can interact with from one of the many clients like <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://getconverse.app">Converse</a>, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.coinbase.com/wallet">Coinbase Wallet</a>, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://lenster.xyz/">Lens</a>, <em>etc</em>. If you’re looking to get started quick, fork the linked <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/nerderlyne/xmtp-llm-bot">GitHub repo</a> and go!</p><div class="relative header-and-anchor"><h2 id="h-prerequisites">Prerequisites</h2></div><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://nodejs.org/"><em>Node.js</em></a></p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://platform.openai.com/docs/api-reference/authentication"><em>OpenAI API Key</em></a></p></li></ul><div class="relative header-and-anchor"><h2 id="h-install-dependencies">Install Dependencies</h2></div><p>First off, let&apos;s install the necessary packages:</p><p><code>pnpm install dotenv @xmtp/xmtp-js ethers openai</code></p><div class="relative header-and-anchor"><h2 id="h-environment-setup">Environment Setup</h2></div><p>Create a <code>.env</code> file to store your XMTP key and OpenAI API key:</p><p><code>KEY=&lt;Your_XMTP_Wallet_Key&gt; OPENAI_API_KEY=&lt;Your_OpenAI_API_Key&gt; XMTP_ENV=&lt;production | dev&gt;</code></p><p>You can grab an OpenAI key on their <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://platform.openai.com/">platform</a>. If you want to use a OSS model like LLAMA 2 for your bot, you can update the <code>baseURL</code> in the constructor:</p><p><code>const llm = new OpenAI({ baseURL: &lt;url_for_your_chat_model&gt; });</code></p><div class="relative header-and-anchor"><h2 id="h-overview-of-code-structure">Overview of Code Structure</h2></div><p>The code consists of three main parts:</p><ol><li><p><strong>Initialization and Configuration</strong>: Importing packages and setting up the environment.</p></li><li><p><strong>Helper Functions</strong>: Functions for creating the XMTP client and fetching conversation history.</p></li><li><p><strong>Chat Handling</strong>: Handling incoming messages and sending replies using OpenAI&apos;s API.</p></li></ol><p><code>import { config } from &quot;dotenv&quot;; import { AttachmentCodec, RemoteAttachmentCodec, } from &quot;@xmtp/content-type-remote-attachment&quot;; import { Client, ListMessagesOptions, SortDirection, DecodedMessage, } from &quot;@xmtp/xmtp-js&quot;; import { utils, Wallet } from &quot;ethers&quot;; import OpenAI from &quot;openai&quot;; config();</code></p><div class="relative header-and-anchor"><h2 id="h-key-components">Key Components</h2></div><div class="relative header-and-anchor"><h3 id="h-xmtp-client-setup">XMTP Client Setup</h3></div><p><code>createClient()</code> initializes an XMTP client, registers codecs for attachments, and publishes user contact information:</p><p><code>async function createClient(): Promise&lt;Client&gt; { let wallet: Wallet; const key = process.env.KEY; if (key) { wallet = new Wallet(key); } else { wallet = Wallet.createRandom(); } if (process.env.XMTP_ENV !== &quot;production&quot; &amp;&amp; process.env.XMTP_ENV !== &quot;dev&quot;) { throw &quot;invalid XMTP env&quot;; } const client = await Client.create(wallet, { env: process.env.XMTP_ENV || &quot;production&quot;, }); // Register the codecs. AttachmentCodec is for local attachments (&lt;1MB) client.registerCodec(new AttachmentCodec()); //RemoteAttachmentCodec is for remote attachments (&gt;1MB) using thirdweb storage client.registerCodec(new RemoteAttachmentCodec()); await client.publishUserContact(); return client; }</code></p><div class="relative header-and-anchor"><h3 id="h-conversation-history">Conversation History</h3></div><p><code>getConversationHistory()</code> fetches the last five messages between the bot and the user and converts them to chat messages interface supported by OpenAI:</p><p><code>const getConversationHistory = async ( client: Client, userAddress: string, ): Promise&lt;OpenAI.Chat.ChatCompletionMessage[]&gt; =&gt; { const conversations = await client.conversations.list(); const conversation = conversations.find((conversation) =&gt; { return ( utils.getAddress(conversation.peerAddress) == utils.getAddress(userAddress) ); }); if (!conversation) { return []; } const options: ListMessagesOptions = { checkAddresses: true, limit: 5, direction: SortDirection.SORT_DIRECTION_DESCENDING, }; const messages = await conversation.messages(options); messages.shift(); if (messages.length === 0) { return []; } return messages .map((message) =&gt; { return { role: message.senderAddress == client.address ? &quot;assistant&quot; : (&quot;user&quot; as OpenAI.Chat.ChatCompletionMessage[&quot;role&quot;]), content: message.content, }; }) .reverse(); };</code></p><div class="relative header-and-anchor"><h3 id="h-handler-context">Handler Context</h3></div><p>The <code>HandlerContext</code> class provides an interface to access message, history, and client information for easier manipulation within the handler:</p><p><code>class HandlerContext { message: DecodedMessage; history: OpenAI.Chat.ChatCompletionMessage[]; client: Client; constructor({ message, history, client }: HandlerContextConstructor) { this.message = message; this.history = history; this.client = client; } async reply(content: any) { await this.message.conversation.send(content); } }</code></p><div class="relative header-and-anchor"><h3 id="h-message-handling">Message Handling</h3></div><p>The <code>handleChat</code> function is the meat of our LLM bot. You can customise the model here, add more context through vector embeddings if you’re building a customer service bot, or use function calls to answer questions about the state of the chain in realtime and so on (see for example, my <em>Check The Chain</em> plugin on the ChatGPT plugin store). A fun, yet easy customisation would be enshrining a <em>soul</em> that really characterizes your bot using a simple system prompt so responses have your desired personality. (After all the goal is to make this something you or your users want to keep talking to!) Leveraging open data, you can even take this a step further and customise the personality based on NFTs and other digital assets held by users—creating a shapeshifter bot that updates with what your users do on their own time! ✨</p><p><code>const handleChat = async (context: HandlerContext) =&gt; { try { if (context.message.contentType.typeId != &quot;text&quot;) { await context.reply(&quot;Sorry, I only understand text messages.&quot;); return; } let messageBody = context.message.content; const messageHistory = context.history; const response = ( await llm.chat.completions.create({ model: &quot;gpt-3.5-turbo-0613&quot;, messages: [ { role: &quot;system&quot;, content: &quot;You are a helpful assistant.&quot;, }, ...messageHistory, { role: &quot;user&quot;, content: messageBody, }, ], }) ).choices[0].message.content; if (!response) { await context.reply( &quot;Sorry, my systems are under repair. Please chat with me later when we are all fixed ♥&quot;, ); return; } await context.reply(response); } catch (error) { console.error(`Error: ${error}`); await context.reply(&quot;Sorry, an error occurred. Please try again later.&quot;); } };</code></p><div class="relative header-and-anchor"><h3 id="h-running-the-bot">Running the Bot</h3></div><p>This is where all our functions come together. The <code>run()</code> function sets up a message stream and invokes the handler whenever a new message is received. It&apos;s wrapped inside a <code>reconnect()</code> for improved error handling.</p><p><code>async function run(handler: Handler) { const client = await createClient(); console.log(`Listening on ${client.address}`); for await (const message of await client.conversations.streamAllMessages()) { try { if (message.senderAddress == client.address) { continue; } const history = await getConversationHistory( client, utils.getAddress(message.senderAddress), ); const context = new HandlerContext({ message, history, client }); await handler(context); } catch (e) { console.log(`error`, e, message); } } }</code></p><div class="relative header-and-anchor"><h2 id="h-running-the-bot">Running the Bot</h2></div><p><code>pnpm run dev</code></p><p>You should now see a message indicating that the bot is listening on a specific XMTP address.</p><img src="https://storage.googleapis.com/papyrus_images/ae6e20a60a2fea69773140d7eb09817d.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAKCAIAAABaL8vzAAAACXBIWXMAABYlAAAWJQFJUiTwAAACKUlEQVR4nGM4t3x9vL17eWjsueXrc71DDs1ePL+qKcnRs8A3rDIs7tXhUwW+YTlewTNLale19vIzMHhoGf26ePPdsXPvjp37du7ar4s3/99+dHXdthgbl5UtPftmzE91883xCn51+NSrQycmZJcxzK9qkmdgjbNzs1PS0BGSTHb1slPS0BeRtlfWtFPSiLNzi7Nzs1ZQi7ZxKvAPZ2Bg6M0s+v/28/8nr/8/ef3r4k0Ienfs3L4Z8y+s2vD8wPEzS9dcX7/19IpNNjKaygzCDJfXbrJVVLdT0dTgF7VWUFPhElDhEigNjUl29SoNjXFU046xcQm3cnDX1A+zsk91842zc4uxcUlz8wu3ctg3Y/7/24/g/vh35S6E8f/2o0trtmpzCZtLqjE8P3DcQ8tIR0jSVFrJVFpJnoHZVlF974y5JxevOrFoxe5pc/fNmL9vxvzd0+ZC2BDyxKIVe2fMfXX4FNwTyOj/7UcXlq/rTUhb3zGFoSet0EfPvDQ0emVLT6qbr4eWUbSNk7WCmreOiZ2Kpp2KpreumbWCmqm0UrSNE8R1cITVdIgFO6fMC9Ux2Tl5HsOU7PJDsxc/P3AcjkDxc/jUswNH4exXMITLRCwWTJ1f7h+2uWc6w//HL7Eq+nv5FpHG/cJmwZzaDj8tk71TFzC8O3aObIN+YUPfzl37//hlT2FtgKHVgRmLGahrOtyC5owiVQZOmlgADqIHO2cs7M4o3NY3CwAv8I+N9PtXyAAAAABJRU5ErkJggg==" nextheight="648" nextwidth="2130" class="image-node embed"><div class="relative header-and-anchor"><h2 id="h-conclusion">Conclusion</h2></div><p>That’s it! This is just scratching the surface, and you can extend this basic bot in numerous ways from customer support bots for protocols and DAOs to leveraging custom content-types for text-to-transaction allowing users to express their intent in natural language.</p><p>Check out the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/nerderlyne/xmtp-llm-bot">Github repo</a> for the full codebase. Talk to a live version of my demo bot by messaging <code>nani.eth</code> ( 0x7AF890Ca7262D6accdA5c9D24AC42e35Bb293188) on any XMTP compatible app.</p><img src="https://images.mirror-media.xyz/publication-images/0Evcl2W4EBhhDWOxonXwu.jpeg?height=2436&amp;width=1125" alt="chatting away with nani.eth on Converse! " title="null" class="image-node embed"><p>🤍</p>]]></content:encoded>
            <author>nerderlyne@newsletter.paragraph.com (nerderlyne)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/ae6e20a60a2fea69773140d7eb09817d.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>