@hybrd/xmtp
v2.0.0
Published
XMTP integration library for Hybrid AI agents. Provides client creation with R2 database persistence, an `XMTPPlugin` for the agent framework, ENS/Basename/address resolvers, and JWT utilities.
Readme
@hybrd/xmtp
XMTP integration library for Hybrid AI agents. Provides client creation with R2 database persistence, an XMTPPlugin for the agent framework, ENS/Basename/address resolvers, and JWT utilities.
Overview
This package handles all XMTP network connectivity for Hybrid agents:
- Client creation with installation revocation, retry logic, and Cloudflare R2 database sync
- XMTPPlugin integrating XMTP into the
Agent+Pluginframework architecture - Resolvers for XMTP inbox IDs, ENS names, and Base network Basenames
- JWT utilities for securing XMTP tool API endpoints
- Scripts for wallet registration and installation management
OpenClaw Compatibility
This package is a Hybrid-native XMTP integration, not derived from the OpenClaw SDK. It wraps @xmtp/node-sdk and @xmtp/agent-sdk with production-ready reliability features.
Architecture
┌──────────────────────────────────────────────────────────────────┐
│ @hybrd/xmtp │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ XMTPPlugin (plugin.ts) │ │
│ │ ┌────────────────────┐ ┌────────────────────────────┐ │ │
│ │ │ XmtpAgent │ │ XmtpClient (low-level) │ │ │
│ │ │ (event listener) │ │ (resolvers, direct API) │ │ │
│ │ └────────────────────┘ └────────────────────────────┘ │ │
│ │ │ │
│ │ Behavior chain: executeBefore → agent.generate → send │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Client (client.ts) │ │
│ │ createXMTPClient() → retry loop: │ │
│ │ 1. Download .db3 from R2 (if XMTP_STORAGE bound) │ │
│ │ 2. Create Client with codecs │ │
│ │ 3. On install limit: revoke old → retry │ │
│ │ 4. On identity error: refresh identity → retry │ │
│ │ 5. Upload .db3 to R2 after connect │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Resolvers (resolver/) │ │
│ │ Resolver → AddressResolver, ENSResolver, BasenameResolver │ │
│ │ XmtpResolver (inbox IDs + message chains) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘Features
Client Creation
import { createXMTPClient } from "@hybrd/xmtp"
const client = await createXMTPClient(process.env.AGENT_WALLET_KEY!, {
persist: true, // Persist conversations locally
maxRetries: 3, // Installation revocation retry limit
storagePath: ".xmtp" // Custom DB storage path
})Automatic resilience:
- If the installation limit is reached, revokes old installations and retries
- If an identity association error occurs, refreshes identity and retries
- If
XMTP_STORAGE(Cloudflare R2) is inglobalThis, downloads the.db3database before connecting and uploads it after — enabling stateless containers to persist XMTP state
Registered content type codecs: Text, Reply, Reaction, WalletSendCalls, TransactionReference
User/Signer Creation
import { createUser, createSigner } from "@hybrd/xmtp"
const user = createUser(process.env.AGENT_WALLET_KEY!)
// { key, account, wallet } — viem wallet client on Sepolia
const signer = createSigner(process.env.AGENT_WALLET_KEY!)
// XMTP Signer: { getIdentifier(), signMessage() }Connection Manager
For long-running services that need health monitoring and auto-reconnection:
import { XMTPConnectionManager } from "@hybrd/xmtp"
const manager = new XMTPConnectionManager(process.env.AGENT_WALLET_KEY!, {
maxRetries: 5,
retryDelayMs: 1000,
healthCheckIntervalMs: 30000,
connectionTimeoutMs: 15000,
reconnectOnFailure: true
})
const client = await manager.connect()
const health = manager.getHealth()
// { isConnected, lastHealthCheck, consecutiveFailures, totalReconnects, avgResponseTime }
await manager.disconnect()XMTPPlugin
Integrates XMTP into the agent framework's Plugin architecture:
import { XMTPPlugin } from "@hybrd/xmtp"
// Applied via agent.use() or agent.listen()
const plugin = XMTPPlugin()
await agent.listen({
port: "8454",
plugins: [plugin],
behaviors: [myBehavior()]
})For each incoming message (text, reply, reaction), the plugin:
- Builds conversation history (up to 20 messages)
- Creates
AgentRuntimewith conversation, message, and xmtpClient - Runs
behaviors.executeBefore(context)— stops chain ifcontext.stopped = true - Calls
agent.generate(messages, { runtime }) - Sets
context.response = reply - Runs
behaviors.executeAfter(context)— stops chain ifcontext.stopped = true - Sends reply (plain text or threaded via
ContentTypeReply)
Resolver
Universal name and address resolution across XMTP, ENS, and Basenames:
import { Resolver } from "@hybrd/xmtp"
import { createPublicClient, http } from "viem"
import { mainnet, base } from "viem/chains"
const resolver = new Resolver({
xmtpClient,
mainnetClient: createPublicClient({ chain: mainnet, transport: http() }),
baseClient: createPublicClient({ chain: base, transport: http() }),
maxCacheSize: 1000,
cacheTtl: 3_600_000 // 1 hour
})
// Universal resolution (tries ENS then Basename)
const address = await resolver.resolveName("vitalik.eth")
const address2 = await resolver.resolveName("myname.base.eth")
// Reverse resolution (tries Basename then ENS)
const name = await resolver.resolveAddressToName("0x...")
// Complete profile
const profile = await resolver.getCompleteProfile("0x...")
// { address, ensName, basename, ensProfile, basenameProfile }
// XMTP-specific
const address3 = await resolver.resolveAddress(inboxId, conversationId)
const sender = await resolver.createXmtpSender(inboxId, conversationId)
// { address, inboxId, name, basename? }
// ENS
const ensProfile = await resolver.getENSProfile("vitalik.eth")
// { ensName, address, avatar, description, twitter, github, url }
// Basenames (Base network)
const basename = await resolver.getBasename("0x...")
const basenameProfile = await resolver.resolveBasenameProfile("0x...")
// Message resolution
const message = await resolver.findMessage(messageId)
const rootMessage = await resolver.findRootMessage(messageId) // Traverses reply chainIndividual Resolvers
import { AddressResolver, ENSResolver, BasenameResolver, XmtpResolver } from "@hybrd/xmtp/resolver"
const ensResolver = new ENSResolver({ mainnetClient })
const basenameResolver = new BasenameResolver({ publicClient: baseClient })
const addressResolver = new AddressResolver({ xmtpClient })
const xmtpResolver = new XmtpResolver({ xmtpClient })JWT Utilities
Secure XMTP tool API endpoints with short-lived JWTs:
import { generateXMTPToolsToken, validateXMTPToolsToken, getValidatedPayload } from "@hybrd/xmtp"
// Generate a 5-minute token (signed with derived secret)
const token = generateXMTPToolsToken({
action: "send",
conversationId: "conv-123",
content: "Hello!"
})
// Validate (returns null if invalid or expired)
const payload = validateXMTPToolsToken(token)
// Extract from Hono request context (Authorization: Bearer or ?token=)
const payload = getValidatedPayload(honoContext)XMTP_API_KEY env var is also accepted as a static alternative to JWT.
Mention Extraction
import { extractSubjects } from "@hybrd/xmtp"
// Extracts @basename.eth and @name.base.eth mentions and resolves to addresses
const subjects = await extractSubjects(messageContent, basenameResolver, ensResolver)
// { "vitalik": "0x...", "myname": "0x..." }Scripts
# Register wallet on XMTP network
hybrid register
# Revoke specific inbox installations
hybrid revoke <inboxId>
# Auto-detect inbox ID and revoke all installations
hybrid revoke-allOr run directly:
pnpm --filter @hybrd/xmtp register
pnpm --filter @hybrd/xmtp revoke
pnpm --filter @hybrd/xmtp revoke-allRe-exports
This package re-exports the complete @xmtp/node-sdk surface and all content type codecs:
import {
Client,
Signer,
type XmtpEnv,
// Content types:
ContentTypeReaction,
ContentTypeReply,
ContentTypeGroupUpdated,
ContentTypeTransactionReference,
ContentTypeWalletSendCalls,
ContentTypeText
} from "@hybrd/xmtp"Database Path Resolution
import { getDbPath } from "@hybrd/xmtp"
const dbPath = await getDbPath("my-agent", "/custom/storage")
// Priority: XMTP_STORAGE_PATH env → storagePath param → .hybrid/.xmtp/
// If XMTP_STORAGE (R2) is in globalThis, downloads existing DB firstEncryption Key Generation
import { generateEncryptionKeyHex } from "@hybrd/xmtp"
const key = generateEncryptionKeyHex() // 32-byte random hex stringEnvironment Variables
| Variable | Description |
|----------|-------------|
| AGENT_WALLET_KEY | Private key for the agent wallet (required) |
| XMTP_ENV | XMTP environment: dev or production (default: dev) |
| XMTP_STORAGE_PATH | Custom path for XMTP database files |
| XMTP_API_KEY | Static API key for XMTP tool endpoints (alternative to JWT) |
| XMTP_DEBUG | Enable debug logging |
Testing
cd packages/xmtp
pnpm testLicense
MIT
