
Research
/Security News
Weaponizing Discord for Command and Control Across npm, PyPI, and RubyGems.org
Socket researchers uncover how threat actors weaponize Discord across the npm, PyPI, and RubyGems ecosystems to exfiltrate sensitive data.
@xmtp/agent-sdk
Advanced tools
Build event‑driven, middleware‑powered messaging agents on the XMTP network. 🚀
Full agent building guide: Build an XMTP Agent
This SDK is based on familiar Node.js patterns: you register event listeners, compose middleware, and extend behavior just like you would in frameworks such as Express. This makes it easy to bring existing JavaScript and TypeScript skills into building conversational agents.
Choose your package manager:
npm install @xmtp/agent-sdk
# or
pnpm add @xmtp/agent-sdk
# or
yarn add @xmtp/agent-sdk
import { Agent } from "@xmtp/agent-sdk";
import { getTestUrl } from "@xmtp/agent-sdk/debug";
import { createUser, createSigner } from "@xmtp/agent-sdk/user";
// 1. Create a local user + signer (you can plug in your own wallet signer)
const user = createUser();
const signer = createSigner(user);
// 2. Spin up the agent
const agent = await Agent.create(signer, {
env: "dev", // or 'production'
dbPath: null, // in-memory store; provide a path to persist
});
// 3. Respond to text messages
agent.on("text", async (ctx) => {
await ctx.sendText("Hello from my XMTP Agent! 👋");
});
// 4. Log when we're ready
agent.on("start", (ctx) => {
console.log(`We are online: ${getTestUrl(ctx.client)}`);
});
await agent.start();
The XMTP Agent SDK allows you to use environment variables (process.env
) for easier configuration without modifying code. Simply set the following variables and call Agent.createFromEnv()
:
Available Variables:
Variable | Purpose | Example |
---|---|---|
XMTP_WALLET_KEY | Private key for wallet | XMTP_WALLET_KEY=0x1234...abcd |
XMTP_ENV | Network environment | XMTP_ENV=dev or XMTP_ENV=production |
XMTP_DB_ENCRYPTION_KEY | Database encryption key | XMTP_DB_ENCRYPTION_KEY=0xabcd...1234 |
Using the environment variables, you can setup your agent in just a few lines of code:
// Load variables from .env file
process.loadEnvFile(".env");
// Create agent using environment variables
const agent = await Agent.createFromEnv();
Agents can also recognize the following environment variables:
Variable | Purpose | Example |
---|---|---|
XMTP_FORCE_DEBUG | Activate debugging logs | XMTP_FORCE_DEBUG=true |
XMTP_FORCE_DEBUG_LEVEL | Specify the logging level (defaults to "info" ) | XMTP_FORCE_DEBUG_LEVEL=debug |
Subscribe only to what you need using Node’s EventEmitter
interface. Events you can listen for:
Message Events
attachment
– an incoming remote attachment messagemessage
– all messages that are not having a custom content typegroup-update
– an incoming group update (like name change, member update, etc.)reaction
– an incoming reaction messagereply
– an incoming reply messagetext
– an incoming text messageunknownMessage
– a message event that does not correspond to any of the pre-implemented event typesConversation Events
conversation
– a new group or DM conversationdm
– a new DM conversationgroup
– a new group conversationLifecycle Events
start
/ stop
– agent lifecycle eventsunhandledError
– unhandled errorsExample
// Listen to specific message types
agent.on("text", async (ctx) => {
console.log(`Text message: ${ctx.message.content}`);
});
agent.on("reaction", async (ctx) => {
console.log(`Reaction: ${ctx.message.content}`);
});
agent.on("reply", async (ctx) => {
console.log(`Reply to: ${ctx.message.content.reference}`);
});
// Listen to new conversations
agent.on("dm", async (ctx) => {
await ctx.conversation.send("Welcome to our DM!");
});
agent.on("group", async (ctx) => {
await ctx.conversation.send("Hello group!");
});
// Listen to unhandled events
agent.on("unhandledError", (error) => {
console.error("Agent error", error);
});
agent.on("unknownMessage", (ctx) => {
console.error("Message type is unknown", ctx);
});
⚠️ Important: The
"message"
event fires for every incoming message, regardless of type. When using the"message"
event, always filter message types to prevent infinite loops. Without proper filtering, your agent might respond to its own messages or react to system messages like read receipts.
Best Practice Example
import { filter } from "@xmtp/agent-sdk";
agent.on("message", async (ctx) => {
// Filter for specific message types
if (filter.isText(ctx.message)) {
await ctx.conversation.send(`Echo: ${ctx.message.content}`);
}
});
Extend your agent with custom business logic using middlewares. Compose cross-cutting behavior like routing, telemetry, rate limiting, analytics, and feature flags, or plug in your own.
Middlewares can be registered with agent.use
either one at a time or as an array. They are executed in the order they were added.
Middleware functions receive a ctx
(context) object and a next
function. Normally, a middleware calls next()
to hand off control to the next one in the chain. However, a middleware can also alter the flow in the following ways:
next()
to continue the chain and pass control to the next middlewarereturn
to stop the chain and prevent events from firingthrow
to trigger the error-handling middleware chainExample
import { Agent, AgentMiddleware, filter } from "@xmtp/agent-sdk";
const onlyText: AgentMiddleware = async (ctx, next) => {
if (filter.isText(ctx.message)) {
// Continue to next middleware
await next();
}
// Break middleware chain
return;
};
const agent = await Agent.createFromEnv();
agent.use(onlyText);
Error middleware can be registered with agent.errors.use
either one at a time or as an array. They are executed in the order they were added.
Error middleware receives the error
, ctx
, and a next
function. Just like regular middleware, the flow in error middleware depends on how to use next
:
next()
to mark the error as handled and continue with the main middleware chainnext(error)
to forward the original (or transformed) error to the next error handlerreturn
to end error handling and stop the middleware chainthrow
to raise a new error to be caught by the error chainExample
import { Agent, AgentErrorMiddleware } from "@xmtp/agent-sdk";
const errorHandler: AgentErrorMiddleware = async (error, ctx, next) => {
if (error instanceof Error) {
// Transform the error and pass it along
await next(`Validation failed: ${error.message}`);
} else {
// Let other error handlers deal with it
await next(error);
}
};
const agent = await Agent.createFromEnv();
agent.errors.use(errorHandler);
Any error not handled by custom error middleware is caught by the default error handler and published to the unhandledError
topic, where it can be observed.
Example
agent.on("unhandledError", (error) => {
console.log("Caught error", error);
});
Instead of manually checking every incoming message, you can use the provided filters.
Example
import { filter } from "@xmtp/agent-sdk";
// Using filter in message handler
agent.on("text", async (ctx) => {
if (filter.isText(ctx.message)) {
await ctx.conversation.send("You sent a text message!");
}
});
// Combine multiple conditions
agent.on("text", async (ctx) => {
if (
filter.hasDefinedContent(ctx.message) &&
!filter.fromSelf(ctx.message, ctx.client) &&
filter.isText(ctx.message)
) {
await ctx.conversation.send("Valid text message received ✅");
}
});
For convenience, the filter
object can also be imported as f
:
// You can import either name:
import { filter, f } from "@xmtp/agent-sdk";
// Both work the same way:
if (f.isText(ctx.message)) {
// Handle message...
}
Available Filters:
You can find all available prebuilt filters here.
Every message event handler receives a MessageContext
with:
message
– the decoded message objectconversation
– the active conversation objectclient
– underlying XMTP clientsendTextReply()
, sendReaction()
, getSenderAddress
, and moreExample
agent.on("text", async (ctx) => {
await ctx.sendTextReply("Reply using helper ✨");
});
These functionalities let you start a conversation:
// Direct Message
const dm = await agent.createDmWithAddress("0x123");
await dm.send("Hello!");
// Group Conversation
const group = await agent.createGroupWithAddresses(["0x123", "0x456"]);
await group.addMembers(["0x789"]);
await group.send("Hello group!");
The Agent SDK comes with subpackages that include utilities. You can for example get a testing URL or details of your Agent from the debug utilities:
import { getTestUrl, logDetails } from "@xmtp/agent-sdk/debug";
// Get a test URL for your agent
const testUrl = getTestUrl(agent.client);
console.log(`Test your agent at: ${testUrl}`);
// Log comprehensive details about your agent
await logDetails(agent.client);
There are also utilities to simplify user management, such as signer creation or name resolutions:
import {
createUser,
createSigner,
createNameResolver,
} from "@xmtp/agent-sdk/user";
// Create a new user with a random private key
const user = createUser();
// Create a signer from the user
const signer = createSigner(user);
// Resolve ENS names or other web3 identities using web3.bio
const resolver = createNameResolver("your-web3bio-api-key");
const address = await resolver("vitalik.eth");
console.log(`Resolved address: ${address}`);
Pass codecs
when creating your agent to extend supported content:
const agent = await Agent.create(signer, {
env: "dev",
dbPath: null,
codecs: [new MyContentType()],
});
LibXMTP is a shared library encapsulating the core functionality of the XMTP messaging protocol, such as cryptography, networking, and language bindings. This version of the Agent SDK uses:
XMTP Node SDK Version | LibXMTP Version |
---|---|
4.2.3 | 1.5.4 |
To verify which LibXMTP version is installed, run npm why @xmtp/node-bindings
after installing the Agent SDK.
We’d love your feedback: open an issue or discussion. PRs welcome for docs, examples, and core improvements.
Build something delightful. Then tell us what you wish was easier.
Happy hacking 💫
FAQs
XMTP Agent SDK for interacting with XMTP networks
The npm package @xmtp/agent-sdk receives a total of 1,684 weekly downloads. As such, @xmtp/agent-sdk popularity was classified as popular.
We found that @xmtp/agent-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
/Security News
Socket researchers uncover how threat actors weaponize Discord across the npm, PyPI, and RubyGems ecosystems to exfiltrate sensitive data.
Security News
Socket now integrates with Bun 1.3’s Security Scanner API to block risky packages at install time and enforce your organization’s policies in local dev and CI.
Research
The Socket Threat Research Team is tracking weekly intrusions into the npm registry that follow a repeatable adversarial playbook used by North Korean state-sponsored actors.