@hybrd/xmtp 1.4.4 โ 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +209 -319
- package/dist/index.cjs +138 -83
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +131 -78
- package/dist/index.js.map +1 -1
- package/package.json +10 -6
- package/src/client.ts +19 -39
- package/src/index.ts +8 -5
- package/src/lib/jwt.ts +16 -23
- package/src/lib/secret.ts +33 -0
- package/src/plugin.ts +98 -39
- package/src/index.ts.old +0 -145
- package/src/lib/message-listener.test.ts.old +0 -369
- package/src/lib/message-listener.ts.old +0 -343
- package/src/localStorage.ts.old +0 -203
- package/src/plugin.filters.test.ts +0 -158
- package/src/service-client.ts.old +0 -309
- package/src/transactionMonitor.ts.old +0 -275
- package/src/types.ts.old +0 -157
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/client.ts","../scripts/revoke-installations.ts","../src/plugin.ts","../src/lib/jwt.ts"],"sourcesContent":["export {\n\tAgent,\n\tcreateSigner,\n\tcreateUser,\n\tfilter,\n\tgetTestUrl\n} from \"@xmtp/agent-sdk\"\n\nexport type * from \"./types\"\n\nexport {\n\tDEFAULT_AMOUNT,\n\tDEFAULT_OPTIONS,\n\tMAX_USDC_AMOUNT\n} from \"./constants\"\n// NodeNext/Node16 requires explicit extensions for relative imports\n// NodeNext/Node16 requires explicit extensions for relative imports\n\n// ===================================================================\n// XMTP Client and Connection Management\n// ===================================================================\nexport {\n createXMTPClient,\n createSigner as createXMTPSigner,\n logAgentDetails,\n validateEnvironment,\n XMTPConnectionManager\n} from \"./client\"\nexport type { XMTPConnectionConfig } from \"./client\"\n\n// ===================================================================\n// XMTP Plugin for Agent Integration\n// ===================================================================\nexport { XMTPPlugin } from \"./plugin\"\nexport type { Plugin } from \"./plugin\"\n\n// ===================================================================\n// JWT Utilities for XMTP Tools\n// ===================================================================\nexport { generateXMTPToolsToken } from \"./lib/jwt\"\nexport type { XMTPToolsPayload } from \"./lib/jwt\"\n\n// ===================================================================\n// XMTP Core SDK Exports\n// ===================================================================\nexport {\n\tClient,\n\tIdentifierKind,\n\t// type Conversation,\n\ttype DecodedMessage,\n\ttype Dm,\n\t// type Group,\n\ttype LogLevel,\n\ttype Signer,\n\ttype XmtpEnv\n} from \"@xmtp/node-sdk\"\n\n// ===================================================================\n// XMTP Content Types\n// ===================================================================\nexport {\n\tContentTypeTransactionReference,\n\ttype TransactionReference\n} from \"@xmtp/content-type-transaction-reference\"\n\nexport { ContentTypeText, type TextParameters } from \"@xmtp/content-type-text\"\n\nexport {\n\tContentTypeReaction,\n\ttype Reaction\n} from \"@xmtp/content-type-reaction\"\n\nexport {\n\tContentTypeReply,\n\tReplyCodec,\n\ttype Reply\n} from \"@xmtp/content-type-reply\"\n\nexport {\n\tContentTypeGroupUpdated,\n\tGroupUpdatedCodec,\n\ttype GroupUpdated\n} from \"@xmtp/content-type-group-updated\"\n\nexport {\n\tContentTypeWalletSendCalls,\n\ttype WalletSendCallsParams\n} from \"@xmtp/content-type-wallet-send-calls\"\n","// ===================================================================\n// Betting Configuration\n// ===================================================================\nexport const DEFAULT_OPTIONS = [\"yes\", \"no\"]\nexport const DEFAULT_AMOUNT = \"0.1\"\nexport const MAX_USDC_AMOUNT = 10 // Maximum allowed USDC transaction amount\n","import { logger } from \"@hybrd/utils\"\nimport { ReactionCodec } from \"@xmtp/content-type-reaction\"\nimport { ReplyCodec } from \"@xmtp/content-type-reply\"\nimport { TransactionReferenceCodec } from \"@xmtp/content-type-transaction-reference\"\nimport { WalletSendCallsCodec } from \"@xmtp/content-type-wallet-send-calls\"\nimport { Client, IdentifierKind, type Signer, XmtpEnv } from \"@xmtp/node-sdk\"\nimport { getRandomValues } from \"node:crypto\"\nimport fs from \"node:fs\"\nimport path from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\nimport { fromString, toString as uint8arraysToString } from \"uint8arrays\"\nimport { createWalletClient, http, toBytes } from \"viem\"\nimport { privateKeyToAccount } from \"viem/accounts\"\nimport { sepolia } from \"viem/chains\"\nimport { revokeOldInstallations } from \"../scripts/revoke-installations\"\nimport { XmtpClient } from \"./types\"\n\n// ===================================================================\n// Module Setup\n// ===================================================================\n// ES module equivalent of __dirname\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = path.dirname(__filename)\n\n// ===================================================================\n// Type Definitions\n// ===================================================================\ninterface User {\n\tkey: `0x${string}`\n\taccount: ReturnType<typeof privateKeyToAccount>\n\twallet: any // Simplified to avoid deep type instantiation\n}\n\n// ===================================================================\n// User and Signer Creation\n// ===================================================================\nexport const createUser = (key: string): User => {\n\tconst account = privateKeyToAccount(key as `0x${string}`)\n\treturn {\n\t\tkey: key as `0x${string}`,\n\t\taccount,\n\t\twallet: createWalletClient({\n\t\t\taccount,\n\t\t\tchain: sepolia,\n\t\t\ttransport: http()\n\t\t})\n\t}\n}\n\nexport const createSigner = (key: string): Signer => {\n\tif (!key || typeof key !== \"string\") {\n\t\tthrow new Error(\"XMTP wallet key must be a non-empty string\")\n\t}\n\tconst sanitizedKey = key.startsWith(\"0x\") ? key : `0x${key}`\n\tconst user = createUser(sanitizedKey)\n\treturn {\n\t\ttype: \"EOA\",\n\t\tgetIdentifier: () => ({\n\t\t\tidentifierKind: 0 as IdentifierKind.Ethereum, // Use numeric value to avoid ambient const enum issue\n\t\t\tidentifier: user.account.address.toLowerCase()\n\t\t}),\n\t\tsignMessage: async (message: string) => {\n\t\t\tconst signature = await user.wallet.signMessage({\n\t\t\t\tmessage,\n\t\t\t\taccount: user.account\n\t\t\t})\n\t\t\treturn toBytes(signature)\n\t\t}\n\t}\n}\n\n// XMTP XmtpClient setup\n// const xmtpClient: XmtpClient | null = null\n\n// Function to clear XMTP database when hitting installation limits\nasync function clearXMTPDatabase(address: string, env: string) {\n\tlogger.debug(\"๐งน Clearing XMTP database to resolve installation limit...\")\n\n\t// Get the storage directory using the same logic as getDbPath\n\tconst getStorageDirectory = () => {\n\t\tconst customStoragePath = process.env.XMTP_STORAGE_PATH\n\n\t\tif (customStoragePath) {\n\t\t\treturn path.isAbsolute(customStoragePath)\n\t\t\t\t? customStoragePath\n\t\t\t\t: path.resolve(process.cwd(), customStoragePath)\n\t\t}\n\n\t\t// Use existing logic as fallback\n\t\tconst projectRoot =\n\t\t\tprocess.env.PROJECT_ROOT || path.resolve(__dirname, \"../../..\")\n\n\t\treturn path.join(projectRoot, \".data/xmtp\") // Local development\n\t}\n\n\t// Clear local database files\n\tconst dbPattern = `${env}-${address}.db3`\n\tconst storageDir = getStorageDirectory()\n\n\t// Primary storage directory\n\tconst possiblePaths = [\n\t\tstorageDir,\n\t\t// Legacy fallback paths for backward compatibility\n\t\tpath.join(process.cwd(), \".data\", \"xmtp\"),\n\t\tpath.join(process.cwd(), \"..\", \".data\", \"xmtp\"),\n\t\tpath.join(process.cwd(), \"..\", \"..\", \".data\", \"xmtp\")\n\t]\n\n\tfor (const dir of possiblePaths) {\n\t\ttry {\n\t\t\tif (fs.existsSync(dir)) {\n\t\t\t\tconst files = fs.readdirSync(dir)\n\t\t\t\tconst matchingFiles = files.filter(\n\t\t\t\t\t(file) =>\n\t\t\t\t\t\tfile.includes(dbPattern) ||\n\t\t\t\t\t\tfile.includes(address) ||\n\t\t\t\t\t\tfile.includes(`xmtp-${env}-${address}`)\n\t\t\t\t)\n\n\t\t\t\tfor (const file of matchingFiles) {\n\t\t\t\t\tconst fullPath = path.join(dir, file)\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfs.unlinkSync(fullPath)\n\t\t\t\t\t\tlogger.debug(`โ
Removed: ${fullPath}`)\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tlogger.debug(`โ ๏ธ Could not remove ${fullPath}:`, err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (err) {\n\t\t\t// Ignore errors when checking directories\n\t\t}\n\t}\n}\n\nexport async function createXMTPClient(\n\tprivateKey: string,\n\topts?: {\n\t\tpersist?: boolean\n\t\tmaxRetries?: number\n\t\tstoragePath?: string\n\t}\n): Promise<XmtpClient> {\n\tconst { persist = true, maxRetries = 3, storagePath } = opts ?? {}\n\tlet attempt = 0\n\n\t// Extract common variables for error handling\n\t// const actualSigner = signer\n\tconst signer = createSigner(privateKey)\n\n\tif (!signer) {\n\t\tthrow new Error(\n\t\t\t\"No signer provided and XMTP_WALLET_KEY environment variable is not set\"\n\t\t)\n\t}\n\n\tconst { XMTP_DB_ENCRYPTION_KEY, XMTP_ENV } = process.env\n\n\t// Get the wallet address to use the correct database\n\tconst identifier = await signer.getIdentifier()\n\tconst address = identifier.identifier\n\n\twhile (attempt < maxRetries) {\n\t\ttry {\n\t\t\tlogger.debug(\n\t\t\t\t`๐ Attempt ${attempt + 1}/${maxRetries} to create XMTP client...`\n\t\t\t)\n\n\t\t\t// Always require encryption key and persistence - no stateless mode\n\t\t\tif (!persist) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Stateless mode is not supported. XMTP client must run in persistent mode \" +\n\t\t\t\t\t\t\"to properly receive and process messages. Set persist: true or remove the persist option \" +\n\t\t\t\t\t\t\"to use the default persistent mode.\"\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif (!XMTP_DB_ENCRYPTION_KEY) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"XMTP_DB_ENCRYPTION_KEY must be set for persistent mode\"\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst dbEncryptionKey = getEncryptionKeyFromHex(XMTP_DB_ENCRYPTION_KEY)\n\t\t\tconst dbPath = await getDbPath(\n\t\t\t\t`${XMTP_ENV || \"dev\"}-${address}`,\n\t\t\t\tstoragePath\n\t\t\t)\n\t\t\tlogger.debug(`๐ Using database path: ${dbPath}`)\n\n\t\t\t// Always create a fresh client and sync it\n\t\t\tconst client = await Client.create(signer, {\n\t\t\t\tdbEncryptionKey,\n\t\t\t\tenv: XMTP_ENV as XmtpEnv,\n\t\t\t\tdbPath,\n\t\t\t\tcodecs: [\n\t\t\t\t\tnew ReplyCodec(),\n\t\t\t\t\tnew ReactionCodec(),\n\t\t\t\t\tnew WalletSendCallsCodec(),\n\t\t\t\t\tnew TransactionReferenceCodec()\n\t\t\t\t]\n\t\t\t})\n\n\t\t\t// Force sync conversations to ensure we have the latest data\n\t\t\tlogger.debug(\"๐ก Syncing conversations to ensure latest state...\")\n\t\t\tawait client.conversations.sync()\n\n\t\t\tawait backupDbToPersistentStorage(\n\t\t\t\tdbPath,\n\t\t\t\t`${XMTP_ENV || \"dev\"}-${address}`\n\t\t\t)\n\n\t\t\tconsole.log(`Wallet: ${address}`)\n\t\t\tconsole.log(`Env: ${XMTP_ENV || \"dev\"}`)\n\t\t\tconsole.log(`Storage: persistent`)\n\n\t\t\treturn client as unknown as XmtpClient\n\t\t} catch (error) {\n\t\t\tattempt++\n\n\t\t\tif (\n\t\t\t\terror instanceof Error &&\n\t\t\t\terror.message.includes(\"5/5 installations\")\n\t\t\t) {\n\t\t\t\tconsole.log(\n\t\t\t\t\t`๐ฅ Installation limit reached (attempt ${attempt}/${maxRetries})`\n\t\t\t\t)\n\n\t\t\t\tif (attempt < maxRetries) {\n\t\t\t\t\t// Get wallet address for database clearing\n\t\t\t\t\tconst identifier = await signer.getIdentifier()\n\t\t\t\t\tconst address = identifier.identifier\n\n\t\t\t\t\t// Extract inboxId from the error message\n\t\t\t\t\tconst inboxIdMatch = error.message.match(/InboxID ([a-f0-9]+)/)\n\t\t\t\t\tconst inboxId = inboxIdMatch ? inboxIdMatch[1] : undefined\n\n\t\t\t\t\t// First try to revoke old installations\n\t\t\t\t\tconst revocationSuccess = await revokeOldInstallations(\n\t\t\t\t\t\tsigner,\n\t\t\t\t\t\tinboxId\n\t\t\t\t\t)\n\n\t\t\t\t\tif (revocationSuccess) {\n\t\t\t\t\t\tconsole.log(\"๐ฏ Installations revoked, retrying connection...\")\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\"โ ๏ธ Installation revocation failed or not needed, clearing database...\"\n\t\t\t\t\t\t)\n\t\t\t\t\t\t// Clear database as fallback\n\t\t\t\t\t\tawait clearXMTPDatabase(address, process.env.XMTP_ENV || \"dev\")\n\t\t\t\t\t}\n\n\t\t\t\t\t// Wait a bit before retrying\n\t\t\t\t\tconst delay = Math.pow(2, attempt) * 1000 // Exponential backoff\n\t\t\t\t\tconsole.log(`โณ Waiting ${delay}ms before retry...`)\n\t\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay))\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\"โ Failed to resolve installation limit after all retries\"\n\t\t\t\t\t)\n\t\t\t\t\tconsole.error(\"๐ก Possible solutions:\")\n\t\t\t\t\tconsole.error(\" 1. Use a different wallet (generate new keys)\")\n\t\t\t\t\tconsole.error(\" 2. Switch XMTP environments (dev <-> production)\")\n\t\t\t\t\tconsole.error(\" 3. Wait and try again later\")\n\t\t\t\t\tconsole.error(\" 4. Contact XMTP support for manual intervention\")\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t} else if (\n\t\t\t\terror instanceof Error &&\n\t\t\t\terror.message.includes(\"Association error: Missing identity update\")\n\t\t\t) {\n\t\t\t\tconsole.log(\n\t\t\t\t\t`๐ Identity association error detected (attempt ${attempt}/${maxRetries})`\n\t\t\t\t)\n\n\t\t\t\tif (attempt < maxRetries) {\n\t\t\t\t\tconsole.log(\"๐ง Attempting automatic identity refresh...\")\n\n\t\t\t\t\t// Try to refresh identity by creating a persistent client first\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconsole.log(\"๐ Creating persistent client to refresh identity...\")\n\t\t\t\t\t\tconst tempEncryptionKey = XMTP_DB_ENCRYPTION_KEY\n\t\t\t\t\t\t\t? getEncryptionKeyFromHex(XMTP_DB_ENCRYPTION_KEY)\n\t\t\t\t\t\t\t: getEncryptionKeyFromHex(generateEncryptionKeyHex())\n\t\t\t\t\t\tconst tempClient = await Client.create(signer, {\n\t\t\t\t\t\t\tdbEncryptionKey: tempEncryptionKey,\n\t\t\t\t\t\t\tenv: XMTP_ENV as XmtpEnv,\n\t\t\t\t\t\t\tdbPath: await getDbPath(\n\t\t\t\t\t\t\t\t`${XMTP_ENV || \"dev\"}-${address}`,\n\t\t\t\t\t\t\t\tstoragePath\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tcodecs: [\n\t\t\t\t\t\t\t\tnew ReplyCodec(),\n\t\t\t\t\t\t\t\tnew ReactionCodec(),\n\t\t\t\t\t\t\t\tnew WalletSendCallsCodec(),\n\t\t\t\t\t\t\t\tnew TransactionReferenceCodec()\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\tconsole.log(\"๐ก Syncing identity and conversations...\")\n\t\t\t\t\t\tawait tempClient.conversations.sync()\n\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\"โ
Identity refresh successful, retrying original request...\"\n\t\t\t\t\t\t)\n\n\t\t\t\t\t\t// Wait a bit before retrying\n\t\t\t\t\t\tconst delay = Math.pow(2, attempt) * 1000 // Exponential backoff\n\t\t\t\t\t\tconsole.log(`โณ Waiting ${delay}ms before retry...`)\n\t\t\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay))\n\t\t\t\t\t} catch (refreshError) {\n\t\t\t\t\t\tconsole.log(`โ Identity refresh failed:`, refreshError)\n\t\t\t\t\t\t// Continue to the retry logic\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\"โ Failed to resolve identity association error after all retries\"\n\t\t\t\t\t)\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\"๐ก Try running: pnpm with-env pnpm --filter @hybrd/xmtp refresh:identity\"\n\t\t\t\t\t)\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// For other errors, don't retry\n\t\t\t\tthrow error\n\t\t\t}\n\t\t}\n\t}\n\n\tthrow new Error(\"Max retries exceeded\")\n}\n\n// ===================================================================\n// Encryption Key Management\n// ===================================================================\n/**\n * Generate a random encryption key\n * @returns The encryption key as a hex string\n */\nexport const generateEncryptionKeyHex = () => {\n\tconst uint8Array = getRandomValues(new Uint8Array(32))\n\treturn uint8arraysToString(uint8Array, \"hex\")\n}\n\n/**\n * Get the encryption key from a hex string\n * @param hex - The hex string\n * @returns The encryption key as Uint8Array\n */\nconst getEncryptionKeyFromHex = (hex: string): Uint8Array => {\n\treturn fromString(hex, \"hex\")\n}\n\n// ===================================================================\n// Database Path Management\n// ===================================================================\nexport const getDbPath = async (description = \"xmtp\", storagePath?: string) => {\n\t// Allow custom storage path via environment variable\n\tconst customStoragePath = process.env.XMTP_STORAGE_PATH\n\n\tlet volumePath: string\n\n\tif (customStoragePath) {\n\t\t// Use custom storage path if provided\n\t\tvolumePath = path.isAbsolute(customStoragePath)\n\t\t\t? customStoragePath\n\t\t\t: path.resolve(process.cwd(), customStoragePath)\n\t} else if (storagePath) {\n\t\tvolumePath = path.isAbsolute(storagePath)\n\t\t\t? storagePath\n\t\t\t: path.resolve(process.cwd(), storagePath)\n\t} else {\n\t\t// Use existing logic as fallback\n\t\tconst projectRoot =\n\t\t\tprocess.env.PROJECT_ROOT || path.resolve(__dirname, \"../../..\")\n\n\t\t// Default storage path for local development\n\t\tvolumePath = path.join(projectRoot, \".data/xmtp\")\n\t}\n\n\tconst dbPath = `${volumePath}/${description}.db3`\n\n\tif (typeof globalThis !== \"undefined\" && \"XMTP_STORAGE\" in globalThis) {\n\t\ttry {\n\t\t\tconsole.log(`๐ฆ Using Cloudflare R2 storage for: ${dbPath}`)\n\n\t\t\tconst r2Bucket = (globalThis as any).XMTP_STORAGE\n\t\t\tconst remotePath = `xmtp-databases/${description}.db3`\n\n\t\t\ttry {\n\t\t\t\tconst existingObject = await r2Bucket.head(remotePath)\n\t\t\t\tif (existingObject) {\n\t\t\t\t\tconsole.log(`๐ฅ Downloading existing database from R2 storage...`)\n\n\t\t\t\t\tif (!fs.existsSync(volumePath)) {\n\t\t\t\t\t\tfs.mkdirSync(volumePath, { recursive: true })\n\t\t\t\t\t}\n\n\t\t\t\t\tconst object = await r2Bucket.get(remotePath)\n\t\t\t\t\tif (object) {\n\t\t\t\t\t\tconst fileData = await object.arrayBuffer()\n\t\t\t\t\t\tfs.writeFileSync(dbPath, new Uint8Array(fileData))\n\t\t\t\t\t\tconsole.log(`โ
Database downloaded from R2 storage`)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(`๐ No existing database found in R2 storage`)\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.log(`โ ๏ธ Failed to download database from R2 storage:`, error)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.log(`โ ๏ธ R2 storage not available:`, error)\n\t\t}\n\t}\n\n\tif (!fs.existsSync(volumePath)) {\n\t\tfs.mkdirSync(volumePath, { recursive: true })\n\t}\n\n\treturn dbPath\n}\n\nconst backupDbToPersistentStorage = async (\n\tdbPath: string,\n\tdescription: string\n) => {\n\tif (\n\t\ttypeof globalThis !== \"undefined\" &&\n\t\t\"XMTP_STORAGE\" in globalThis &&\n\t\tfs.existsSync(dbPath)\n\t) {\n\t\ttry {\n\t\t\tconsole.log(`๐ฆ Backing up database to R2 storage: ${dbPath}`)\n\n\t\t\tconst r2Bucket = (globalThis as any).XMTP_STORAGE\n\t\t\tconst remotePath = `xmtp-databases/${description}.db3`\n\n\t\t\tconst fileData = fs.readFileSync(dbPath)\n\t\t\tawait r2Bucket.put(remotePath, fileData)\n\t\t\tconsole.log(`โ
Database backed up to R2 storage: ${remotePath}`)\n\t\t} catch (error) {\n\t\t\tconsole.log(`โ ๏ธ Failed to backup database to R2 storage:`, error)\n\t\t}\n\t}\n}\n\n// ===================================================================\n// Logging and Debugging\n// ===================================================================\nexport const logAgentDetails = async (\n\tclients: XmtpClient | XmtpClient[]\n): Promise<void> => {\n\tconst clientsByAddress = Array.isArray(clients)\n\t\t? clients.reduce<Record<string, XmtpClient[]>>((acc, XmtpClient) => {\n\t\t\t\tconst address = XmtpClient.accountIdentifier?.identifier ?? \"\"\n\t\t\t\tacc[address] = acc[address] ?? []\n\t\t\t\tacc[address].push(XmtpClient)\n\t\t\t\treturn acc\n\t\t\t}, {})\n\t\t: {\n\t\t\t\t[clients.accountIdentifier?.identifier ?? \"\"]: [clients]\n\t\t\t}\n\n\tfor (const [address, clientGroup] of Object.entries(clientsByAddress)) {\n\t\tconst firstClient = clientGroup[0]\n\t\tconst inboxId = firstClient?.inboxId\n\t\tconst environments = clientGroup\n\t\t\t.map((c) => c.options?.env ?? \"dev\")\n\t\t\t.join(\", \")\n\t\tconsole.log(`\\x1b[38;2;252;76;52m\n โโโ โโโโโโโ โโโโโโโโโโโโโโโโโโโโ \n โโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ\n โโโโโโ โโโโโโโโโโโ โโโ โโโโโโโโ\n โโโโโโ โโโโโโโโโโโ โโโ โโโโโโโ \n โโโโ โโโโโโ โโโ โโโ โโโ โโโ \n โโโ โโโโโโ โโโ โโโ โโโ \n \\x1b[0m`)\n\n\t\tconst urls = [`http://xmtp.chat/dm/${address}`]\n\n\t\tconst conversations = await firstClient?.conversations.list()\n\n\t\tconsole.log(`\n โ XMTP XmtpClient:\n โข Address: ${address}\n โข Conversations: ${conversations?.length}\n โข InboxId: ${inboxId}\n โข Networks: ${environments}\n ${urls.map((url) => `โข URL: ${url}`).join(\"\\n\")}`)\n\t}\n}\n\n// ===================================================================\n// Environment Validation\n// ===================================================================\nexport function validateEnvironment(vars: string[]): Record<string, string> {\n\tconst missing = vars.filter((v) => !process.env[v])\n\n\tif (missing.length) {\n\t\ttry {\n\t\t\tconst envPath = path.resolve(process.cwd(), \".env\")\n\t\t\tif (fs.existsSync(envPath)) {\n\t\t\t\tconst envVars = fs\n\t\t\t\t\t.readFileSync(envPath, \"utf-8\")\n\t\t\t\t\t.split(\"\\n\")\n\t\t\t\t\t.filter((line) => line.trim() && !line.startsWith(\"#\"))\n\t\t\t\t\t.reduce<Record<string, string>>((acc, line) => {\n\t\t\t\t\t\tconst [key, ...val] = line.split(\"=\")\n\t\t\t\t\t\tif (key && val.length) acc[key.trim()] = val.join(\"=\").trim()\n\t\t\t\t\t\treturn acc\n\t\t\t\t\t}, {})\n\n\t\t\t\tmissing.forEach((v) => {\n\t\t\t\t\tif (envVars[v]) process.env[v] = envVars[v]\n\t\t\t\t})\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(e)\n\t\t\t/* ignore errors */\n\t\t}\n\n\t\tconst stillMissing = vars.filter((v) => !process.env[v])\n\t\tif (stillMissing.length) {\n\t\t\tconsole.error(\"Missing env vars:\", stillMissing.join(\", \"))\n\t\t\tprocess.exit(1)\n\t\t}\n\t}\n\n\treturn vars.reduce<Record<string, string>>((acc, key) => {\n\t\tacc[key] = process.env[key] as string\n\t\treturn acc\n\t}, {})\n}\n\n/**\n * Diagnose XMTP environment and identity issues (internal use only)\n */\nasync function diagnoseXMTPIdentityIssue(\n\tclient: XmtpClient,\n\tinboxId: string,\n\tenvironment: string\n): Promise<{\n\tcanResolve: boolean\n\tsuggestions: string[]\n\tdetails: Record<string, any>\n}> {\n\tconst suggestions: string[] = []\n\tconst details: Record<string, any> = {\n\t\tenvironment,\n\t\tinboxId,\n\t\ttimestamp: new Date().toISOString()\n\t}\n\n\ttry {\n\t\t// Try to resolve the inbox state\n\t\tconst inboxState = await client.preferences.inboxStateFromInboxIds([\n\t\t\tinboxId\n\t\t])\n\n\t\tif (inboxState.length === 0) {\n\t\t\tsuggestions.push(\n\t\t\t\t`Inbox ID ${inboxId} not found in ${environment} environment`\n\t\t\t)\n\t\t\tsuggestions.push(\n\t\t\t\t\"Try switching XMTP_ENV to 'dev' if currently 'production' or vice versa\"\n\t\t\t)\n\t\t\tsuggestions.push(\n\t\t\t\t\"Verify the user has created an identity on this XMTP network\"\n\t\t\t)\n\t\t\tdetails.inboxStateFound = false\n\t\t\treturn { canResolve: false, suggestions, details }\n\t\t}\n\n\t\tconst inbox = inboxState[0]\n\t\tif (!inbox) {\n\t\t\tsuggestions.push(\"Inbox state returned empty data\")\n\t\t\tdetails.inboxStateFound = false\n\t\t\treturn { canResolve: false, suggestions, details }\n\t\t}\n\n\t\tdetails.inboxStateFound = true\n\t\tdetails.identifierCount = inbox.identifiers?.length || 0\n\n\t\tif (!inbox.identifiers || inbox.identifiers.length === 0) {\n\t\t\tsuggestions.push(\"Inbox found but has no identifiers\")\n\t\t\tsuggestions.push(\"This indicates incomplete identity registration\")\n\t\t\tsuggestions.push(\"User may need to re-register their identity on XMTP\")\n\t\t\tdetails.hasIdentifiers = false\n\t\t\treturn { canResolve: false, suggestions, details }\n\t\t}\n\n\t\t// Successfully resolved\n\t\tdetails.hasIdentifiers = true\n\t\tdetails.resolvedAddress = inbox.identifiers[0]?.identifier\n\t\treturn {\n\t\t\tcanResolve: true,\n\t\t\tsuggestions: [\"Identity resolved successfully\"],\n\t\t\tdetails\n\t\t}\n\t} catch (error) {\n\t\tconst errorMessage = error instanceof Error ? error.message : String(error)\n\t\tdetails.error = errorMessage\n\n\t\tif (errorMessage.includes(\"Association error\")) {\n\t\t\tsuggestions.push(\"XMTP identity association error detected\")\n\t\t\tsuggestions.push(\n\t\t\t\t\"Check if user exists on the correct XMTP environment (dev vs production)\"\n\t\t\t)\n\t\t\tsuggestions.push(\n\t\t\t\t\"Identity may need to be recreated on the current environment\"\n\t\t\t)\n\t\t}\n\n\t\tif (errorMessage.includes(\"Missing identity update\")) {\n\t\t\tsuggestions.push(\"Missing identity updates in XMTP network\")\n\t\t\tsuggestions.push(\"This can indicate network sync issues\")\n\t\t\tsuggestions.push(\"Wait a few minutes and retry, or recreate identity\")\n\t\t}\n\n\t\tif (errorMessage.includes(\"database\") || errorMessage.includes(\"storage\")) {\n\t\t\tsuggestions.push(\"XMTP local database/storage issue\")\n\t\t\tsuggestions.push(\"Try clearing XMTP database and resyncing\")\n\t\t\tsuggestions.push(\"Check .data/xmtp directory permissions\")\n\t\t}\n\n\t\tsuggestions.push(\"Consider testing with a fresh XMTP identity\")\n\t\treturn { canResolve: false, suggestions, details }\n\t}\n}\n\n// ===================================================================\n// Enhanced Connection Management & Health Monitoring\n// ===================================================================\n\nexport interface XMTPConnectionConfig {\n\tmaxRetries?: number\n\tretryDelayMs?: number\n\thealthCheckIntervalMs?: number\n\tconnectionTimeoutMs?: number\n\treconnectOnFailure?: boolean\n}\n\nexport interface XMTPConnectionHealth {\n\tisConnected: boolean\n\tlastHealthCheck: Date\n\tconsecutiveFailures: number\n\ttotalReconnects: number\n\tavgResponseTime: number\n}\n\nexport class XMTPConnectionManager {\n\tprivate client: XmtpClient | null = null\n\tprivate privateKey: string\n\tprivate config: Required<XMTPConnectionConfig>\n\tprivate health: XMTPConnectionHealth\n\tprivate healthCheckTimer: NodeJS.Timeout | null = null\n\tprivate isReconnecting = false\n\n\tconstructor(privateKey: string, config: XMTPConnectionConfig = {}) {\n\t\tthis.privateKey = privateKey\n\t\tthis.config = {\n\t\t\tmaxRetries: config.maxRetries ?? 5,\n\t\t\tretryDelayMs: config.retryDelayMs ?? 1000,\n\t\t\thealthCheckIntervalMs: config.healthCheckIntervalMs ?? 30000,\n\t\t\tconnectionTimeoutMs: config.connectionTimeoutMs ?? 10000,\n\t\t\treconnectOnFailure: config.reconnectOnFailure ?? true\n\t\t}\n\n\t\tthis.health = {\n\t\t\tisConnected: false,\n\t\t\tlastHealthCheck: new Date(),\n\t\t\tconsecutiveFailures: 0,\n\t\t\ttotalReconnects: 0,\n\t\t\tavgResponseTime: 0\n\t\t}\n\t}\n\n\tasync connect(persist = false): Promise<XmtpClient> {\n\t\tif (this.client && this.health.isConnected) {\n\t\t\treturn this.client\n\t\t}\n\n\t\tlet attempt = 0\n\t\twhile (attempt < this.config.maxRetries) {\n\t\t\ttry {\n\t\t\t\tconsole.log(\n\t\t\t\t\t`๐ XMTP connection attempt ${attempt + 1}/${this.config.maxRetries}`\n\t\t\t\t)\n\n\t\t\t\tthis.client = await createXMTPClient(this.privateKey, { persist })\n\t\t\t\tthis.health.isConnected = true\n\t\t\t\tthis.health.consecutiveFailures = 0\n\n\t\t\t\t// Start health monitoring\n\t\t\t\tthis.startHealthMonitoring()\n\n\t\t\t\tconsole.log(\"โ
XMTP client connected successfully\")\n\t\t\t\treturn this.client\n\t\t\t} catch (error) {\n\t\t\t\tattempt++\n\t\t\t\tthis.health.consecutiveFailures++\n\n\t\t\t\tconsole.error(`โ XMTP connection attempt ${attempt} failed:`, error)\n\n\t\t\t\tif (attempt < this.config.maxRetries) {\n\t\t\t\t\tconst delay = this.config.retryDelayMs * Math.pow(2, attempt - 1)\n\t\t\t\t\tconsole.log(`โณ Retrying in ${delay}ms...`)\n\t\t\t\t\tawait this.sleep(delay)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`Failed to connect to XMTP after ${this.config.maxRetries} attempts`\n\t\t)\n\t}\n\n\t// private async createClientWithTimeout(persist: boolean): Promise<XmtpClient> {\n\t// const timeoutPromise = new Promise<never>((_, reject) => {\n\t// setTimeout(\n\t// () => reject(new Error(\"Connection timeout\")),\n\t// this.config.connectionTimeoutMs\n\t// )\n\t// })\n\n\t// const clientPromise = createXMTPClient(this.signer, { persist })\n\n\t// return Promise.race([clientPromise, timeoutPromise])\n\t// }\n\n\tprivate startHealthMonitoring(): void {\n\t\tif (this.healthCheckTimer) {\n\t\t\tclearInterval(this.healthCheckTimer)\n\t\t}\n\n\t\tthis.healthCheckTimer = setInterval(() => {\n\t\t\tthis.performHealthCheck()\n\t\t}, this.config.healthCheckIntervalMs)\n\t}\n\n\tprivate async performHealthCheck(): Promise<void> {\n\t\tif (!this.client) return\n\n\t\tconst startTime = Date.now()\n\n\t\ttry {\n\t\t\t// Simple health check: try to list conversations\n\t\t\tawait this.client.conversations.list()\n\n\t\t\tconst responseTime = Date.now() - startTime\n\t\t\tthis.health.avgResponseTime =\n\t\t\t\t(this.health.avgResponseTime + responseTime) / 2\n\t\t\tthis.health.lastHealthCheck = new Date()\n\t\t\tthis.health.consecutiveFailures = 0\n\t\t\tthis.health.isConnected = true\n\n\t\t\tconsole.log(`๐ XMTP health check passed (${responseTime}ms)`)\n\t\t} catch (error) {\n\t\t\tthis.health.consecutiveFailures++\n\t\t\tthis.health.isConnected = false\n\n\t\t\tconsole.error(`๐ XMTP health check failed:`, error)\n\n\t\t\t// Trigger reconnection if enabled\n\t\t\tif (this.config.reconnectOnFailure && !this.isReconnecting) {\n\t\t\t\tthis.handleConnectionFailure()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async handleConnectionFailure(): Promise<void> {\n\t\tif (this.isReconnecting) return\n\n\t\tthis.isReconnecting = true\n\t\tthis.health.totalReconnects++\n\n\t\tconsole.log(\"๐ XMTP connection lost, attempting to reconnect...\")\n\n\t\ttry {\n\t\t\tthis.client = null\n\t\t\tawait this.connect()\n\t\t\tconsole.log(\"โ
XMTP reconnection successful\")\n\t\t} catch (error) {\n\t\t\tconsole.error(\"โ XMTP reconnection failed:\", error)\n\t\t} finally {\n\t\t\tthis.isReconnecting = false\n\t\t}\n\t}\n\n\tprivate sleep(ms: number): Promise<void> {\n\t\treturn new Promise((resolve) => setTimeout(resolve, ms))\n\t}\n\n\tgetHealth(): XMTPConnectionHealth {\n\t\treturn { ...this.health }\n\t}\n\n\tgetClient(): XmtpClient | null {\n\t\treturn this.client\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tif (this.healthCheckTimer) {\n\t\t\tclearInterval(this.healthCheckTimer)\n\t\t\tthis.healthCheckTimer = null\n\t\t}\n\n\t\tthis.client = null\n\t\tthis.health.isConnected = false\n\t\tconsole.log(\"๐ XMTP client disconnected\")\n\t}\n}\n\n// Enhanced client creation with connection management\nexport async function createXMTPConnectionManager(\n\tprivateKey: string,\n\tconfig?: XMTPConnectionConfig\n): Promise<XMTPConnectionManager> {\n\tconst manager = new XMTPConnectionManager(privateKey, config)\n\tawait manager.connect()\n\treturn manager\n}\n\n// ===================================================================\n// User Address Resolution with Auto-Refresh\n// ===================================================================\n","import { Client, type Signer } from \"@xmtp/node-sdk\"\nimport { createSigner } from \"../src/client\"\n\n// Function to revoke old installations when hitting the limit\nexport async function revokeOldInstallations(signer: Signer, inboxId?: string) {\n\tconsole.log(\"๐ง Attempting to revoke old installations...\")\n\n\ttry {\n\t\t// If we don't have the inboxId, we need to extract it from a temporary client attempt\n\t\tif (!inboxId) {\n\t\t\tconsole.log(\"โน๏ธ No inboxId provided, cannot revoke installations\")\n\t\t\treturn false\n\t\t}\n\n\t\tconst inboxStates = await Client.inboxStateFromInboxIds(\n\t\t\t[inboxId],\n\t\t\tprocess.env.XMTP_ENV as \"dev\" | \"production\"\n\t\t)\n\n\t\tif (!inboxStates[0]) {\n\t\t\tconsole.log(\"โ No inbox state found for the provided inboxId\")\n\t\t\treturn false\n\t\t}\n\n\t\tconst toRevokeInstallationBytes = inboxStates[0].installations.map(\n\t\t\t(i: { bytes: Uint8Array }) => i.bytes\n\t\t)\n\n\t\tawait Client.revokeInstallations(\n\t\t\tsigner,\n\t\t\tinboxId,\n\t\t\ttoRevokeInstallationBytes,\n\t\t\tprocess.env.XMTP_ENV as \"dev\" | \"production\"\n\t\t)\n\n\t\tconst resultingStates = await Client.inboxStateFromInboxIds(\n\t\t\t[inboxId],\n\t\t\tprocess.env.XMTP_ENV as \"dev\" | \"production\"\n\t\t)\n\n\t\tconsole.log(\n\t\t\t`๐ Revoked installations: ${toRevokeInstallationBytes.length} installations`\n\t\t)\n\t\tconsole.log(\n\t\t\t`๐ Resulting state: ${resultingStates[0]?.installations.length || 0} installations`\n\t\t)\n\n\t\treturn true\n\t} catch (error) {\n\t\tconsole.error(\"โ Error during installation revocation:\", error)\n\t\treturn false\n\t}\n}\n\n// CLI script to revoke installations\nasync function main() {\n\tconst { XMTP_WALLET_KEY } = process.env\n\tconst inboxId = process.argv[2]\n\n\tif (!XMTP_WALLET_KEY) {\n\t\tconsole.error(\"โ XMTP_WALLET_KEY is required\")\n\t\tprocess.exit(1)\n\t}\n\n\tif (!inboxId) {\n\t\tconsole.error(\"โ InboxID is required as CLI argument\")\n\t\tconsole.error(\"Usage: tsx revoke-installations.ts <inboxId>\")\n\t\tprocess.exit(1)\n\t}\n\n\tconst signer = createSigner(XMTP_WALLET_KEY)\n\tconst identifier = await signer.getIdentifier()\n\tconst address = identifier.identifier\n\n\tconsole.log(`๐ Wallet Address: ${address}`)\n\tconsole.log(`๐ Inbox ID: ${inboxId}`)\n\n\t// Try to revoke installations\n\tconst success = await revokeOldInstallations(signer, inboxId)\n\n\tif (success) {\n\t\tconsole.log(\"โ
Successfully revoked installations\")\n\t} else {\n\t\tconsole.log(\"โ Failed to revoke installations\")\n\t\tprocess.exit(1)\n\t}\n}\n\n// Run if called directly\nif (import.meta.url === `file://${process.argv[1]}`) {\n\tmain().catch((error) => {\n\t\tconsole.error(\"๐ฅ Fatal error:\", error)\n\t\tprocess.exit(1)\n\t})\n}\n","import {\n\tAgent as XmtpAgent,\n\tXmtpEnv,\n\tcreateSigner,\n\tcreateUser\n} from \"@xmtp/agent-sdk\"\n\nimport type {\n\tAgentMessage,\n\tAgentRuntime,\n\tBehaviorContext,\n\tBehaviorRegistry,\n\tPlugin,\n\tPluginContext,\n\tXmtpClient,\n\tXmtpConversation,\n\tXmtpMessage\n} from \"@hybrd/types\"\nimport { logger } from \"@hybrd/utils\"\nimport { randomUUID } from \"node:crypto\"\nimport { createXMTPClient, getDbPath } from \"./client\"\nimport { ContentTypeReply, ContentTypeText, type Reply } from \"./index\"\n\n// Re-export types from @hybrd/types for backward compatibility\nexport type { Plugin }\n\n/**\n * Send a response with threading support\n */\nasync function sendResponse(\n\tconversation: XmtpConversation,\n\ttext: string,\n\toriginalMessageId: string,\n\tbehaviorContext?: BehaviorContext\n) {\n\tconst shouldThread = behaviorContext?.sendOptions?.threaded ?? false\n\n\tif (shouldThread) {\n\t\t// Send as a reply to the original message\n\t\ttry {\n\t\t\tconst reply: Reply = {\n\t\t\t\treference: originalMessageId,\n\t\t\t\tcontentType: ContentTypeText,\n\t\t\t\tcontent: text\n\t\t\t}\n\t\t\tawait conversation.send(reply, ContentTypeReply)\n\t\t\tlogger.debug(\n\t\t\t\t`โ
[sendResponse] Threaded reply sent successfully to message ${originalMessageId}`\n\t\t\t)\n\t\t} catch (error) {\n\t\t\tlogger.error(\n\t\t\t\t`โ [sendResponse] Failed to send threaded reply to message ${originalMessageId}:`,\n\t\t\t\terror\n\t\t\t)\n\t\t\t// Fall back to regular message if threaded reply fails\n\t\t\tlogger.debug(`๐ [sendResponse] Falling back to regular message`)\n\t\t\tawait conversation.send(text)\n\t\t}\n\t} else {\n\t\t// Send as a regular message\n\t\tawait conversation.send(text)\n\t}\n}\n\n/**\n * XMTP Plugin that provides XMTP functionality to the agent\n *\n * @description\n * This plugin integrates XMTP messaging capabilities into the agent's\n * HTTP server. It mounts the XMTP endpoints for handling XMTP tools requests.\n */\nexport function XMTPPlugin(): Plugin<PluginContext> {\n\treturn {\n\t\tname: \"xmtp\",\n\t\tdescription: \"Provides XMTP messaging functionality\",\n\t\tapply: async (app, context): Promise<void> => {\n\t\t\tconst {\n\t\t\t\tXMTP_WALLET_KEY,\n\t\t\t\tXMTP_DB_ENCRYPTION_KEY,\n\t\t\t\tXMTP_ENV = \"production\"\n\t\t\t} = process.env\n\n\t\t\tconst { agent } = context\n\t\t\tconst pluginContext = context as PluginContext & {\n\t\t\t\tbehaviors?: BehaviorRegistry\n\t\t\t}\n\n\t\t\tif (!XMTP_WALLET_KEY) {\n\t\t\t\tthrow new Error(\"XMTP_WALLET_KEY must be set\")\n\t\t\t}\n\n\t\t\tif (!XMTP_DB_ENCRYPTION_KEY) {\n\t\t\t\tthrow new Error(\"XMTP_DB_ENCRYPTION_KEY must be set\")\n\t\t\t}\n\n\t\t\tconst user = createUser(XMTP_WALLET_KEY as `0x${string}`)\n\t\t\tconst signer = createSigner(user)\n\n\t\t\tconst xmtpClient = await createXMTPClient(\n\t\t\t\tXMTP_WALLET_KEY as `0x${string}`\n\t\t\t)\n\n\t\t\tconst address = user.account.address.toLowerCase()\n\t\t\tconst agentDbPath = await getDbPath(\n\t\t\t\t`agent-${XMTP_ENV || \"dev\"}-${address}`\n\t\t\t)\n\t\t\tlogger.debug(`๐ Using database path: ${agentDbPath}`)\n\n\t\t\tconst xmtp = await XmtpAgent.create(signer, {\n\t\t\t\tenv: XMTP_ENV as XmtpEnv,\n\t\t\t\tdbPath: agentDbPath\n\t\t\t})\n\n\t\t\txmtp.on(\"reaction\", async ({ conversation, message }) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst text = message.content.content\n\t\t\t\t\tconst messages: AgentMessage[] = [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: randomUUID(),\n\t\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\t\tparts: [{ type: \"text\", text }]\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\n\t\t\t\t\tconst baseRuntime: AgentRuntime = {\n\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\txmtpClient\n\t\t\t\t\t}\n\n\t\t\t\t\tconst runtime = await agent.createRuntimeContext(baseRuntime)\n\n\t\t\t\t\t// Execute pre-response behaviors\n\t\t\t\t\tif (context.behaviors) {\n\t\t\t\t\t\tconst behaviorContext: BehaviorContext = {\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait context.behaviors.executeBefore(behaviorContext)\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { text: reply } = await agent.generate(messages, { runtime })\n\n\t\t\t\t\t// Execute post-response behaviors\n\t\t\t\t\tlet behaviorContext: BehaviorContext | undefined\n\t\t\t\t\tif (context.behaviors) {\n\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\t\tresponse: reply\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait context.behaviors.executeAfter(behaviorContext)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Create minimal context for send options\n\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\t\tresponse: reply\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check if message was filtered out by filterMessages behavior\n\t\t\t\t\tif (behaviorContext?.sendOptions?.filtered) {\n\t\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t\t`๐ [XMTP Plugin] Skipping reaction response due to message being filtered`\n\t\t\t\t\t\t)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tawait sendResponse(\n\t\t\t\t\t\tconversation as unknown as XmtpConversation,\n\t\t\t\t\t\treply,\n\t\t\t\t\t\tmessage.id,\n\t\t\t\t\t\tbehaviorContext\n\t\t\t\t\t)\n\t\t\t\t} catch (err) {\n\t\t\t\t\tlogger.error(\"โ Error handling reaction:\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\txmtp.on(\"reply\", async ({ conversation, message }) => {\n\t\t\t\ttry {\n\t\t\t\t\t// TODO - why isn't this typed better?\n\t\t\t\t\tconst text = message.content.content as string\n\t\t\t\t\tconst messages: AgentMessage[] = [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: randomUUID(),\n\t\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\t\tparts: [{ type: \"text\", text }]\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\n\t\t\t\t\tconst baseRuntime: AgentRuntime = {\n\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\txmtpClient\n\t\t\t\t\t}\n\n\t\t\t\t\tconst runtime = await agent.createRuntimeContext(baseRuntime)\n\n\t\t\t\t\t// Execute pre-response behaviors\n\t\t\t\t\tlet behaviorContext: BehaviorContext | undefined\n\t\t\t\t\tif (context.behaviors) {\n\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait context.behaviors.executeBefore(behaviorContext)\n\n\t\t\t\t\t\t// Check if behaviors were stopped early (e.g., due to filtering)\n\t\t\t\t\t\tif (behaviorContext.stopped) {\n\t\t\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t\t\t`๐ [XMTP Plugin] Skipping reply response due to behavior chain being stopped`\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { text: reply } = await agent.generate(messages, { runtime })\n\n\t\t\t\t\t// Execute post-response behaviors\n\t\t\t\t\tif (context.behaviors) {\n\t\t\t\t\t\tif (!behaviorContext) {\n\t\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\t\t\tresponse: reply\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tbehaviorContext.response = reply\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait context.behaviors.executeAfter(behaviorContext)\n\n\t\t\t\t\t\t// Check if post behaviors were stopped early\n\t\t\t\t\t\tif (behaviorContext.stopped) {\n\t\t\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t\t\t`๐ [XMTP Plugin] Skipping reply response due to post-behavior chain being stopped`\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Create minimal context for send options\n\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\t\tresponse: reply\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tawait sendResponse(\n\t\t\t\t\t\tconversation as unknown as XmtpConversation,\n\t\t\t\t\t\treply,\n\t\t\t\t\t\tmessage.id,\n\t\t\t\t\t\tbehaviorContext\n\t\t\t\t\t)\n\t\t\t\t} catch (err) {\n\t\t\t\t\tlogger.error(\"โ Error handling reply:\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\txmtp.on(\"text\", async ({ conversation, message }) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst text = message.content\n\t\t\t\t\tconst messages: AgentMessage[] = [\n\t\t\t\t\t\t{ id: randomUUID(), role: \"user\", parts: [{ type: \"text\", text }] }\n\t\t\t\t\t]\n\n\t\t\t\t\tconst baseRuntime: AgentRuntime = {\n\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\txmtpClient\n\t\t\t\t\t}\n\n\t\t\t\t\tconst runtime = await agent.createRuntimeContext(baseRuntime)\n\n\t\t\t\t\t// Execute pre-response behaviors\n\t\t\t\t\tlet behaviorContext: BehaviorContext | undefined\n\t\t\t\t\tif (context.behaviors) {\n\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait context.behaviors.executeBefore(behaviorContext)\n\n\t\t\t\t\t\t// Check if behaviors were stopped early (e.g., due to filtering)\n\t\t\t\t\t\tif (behaviorContext.stopped) {\n\t\t\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t\t\t`๐ [XMTP Plugin] Skipping text response due to behavior chain being stopped`\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { text: reply } = await agent.generate(messages, { runtime })\n\n\t\t\t\t\t// Execute post-response behaviors\n\t\t\t\t\tif (context.behaviors) {\n\t\t\t\t\t\tif (!behaviorContext) {\n\t\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\t\t\tresponse: reply\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tbehaviorContext.response = reply\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait context.behaviors.executeAfter(behaviorContext)\n\n\t\t\t\t\t\t// Check if post behaviors were stopped early\n\t\t\t\t\t\tif (behaviorContext.stopped) {\n\t\t\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t\t\t`๐ [XMTP Plugin] Skipping text response due to post-behavior chain being stopped`\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Create minimal context for send options\n\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\t\tresponse: reply\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tawait sendResponse(\n\t\t\t\t\t\tconversation as unknown as XmtpConversation,\n\t\t\t\t\t\treply,\n\t\t\t\t\t\tmessage.id,\n\t\t\t\t\t\tbehaviorContext\n\t\t\t\t\t)\n\t\t\t\t} catch (err) {\n\t\t\t\t\tlogger.error(\"โ Error handling text:\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// Event handlers removed due to incompatibility with current XMTP agent SDK\n\n\t\t\tvoid xmtp\n\t\t\t\t.start()\n\t\t\t\t.then(() => logger.debug(\"โ
XMTP agent listener started\"))\n\t\t\t\t.catch((err) =>\n\t\t\t\t\tconsole.error(\"โ XMTP agent listener failed to start:\", err)\n\t\t\t\t)\n\t\t}\n\t}\n}\n","import { Context } from \"hono\"\nimport jwt from \"jsonwebtoken\"\nimport { logger } from \"@hybrd/utils\"\n\nexport interface XMTPToolsPayload {\n\taction: \"send\" | \"reply\" | \"react\" | \"transaction\" | \"blockchain-event\"\n\tconversationId: string\n\t// Action-specific data\n\tcontent?: string\n\treferenceMessageId?: string\n\temoji?: string\n\tactionType?: \"added\" | \"removed\"\n\tfromAddress?: string\n\tchainId?: string\n\tcalls?: Array<{\n\t\tto: string\n\t\tdata: string\n\t\tmetadata?: {\n\t\t\tdescription: string\n\t\t\ttransactionType: string\n\t\t}\n\t}>\n\t// Metadata\n\tissued: number\n\texpires: number\n}\n\n/**\n * Validates token and returns payload for both GET and POST endpoints\n *\n * @param {Context} c - Hono context object containing request information\n * @returns {XMTPToolsPayload | null} The validated payload or null if invalid\n *\n * @description\n * Supports two authentication methods:\n * - Authorization header with Bearer token (for POST endpoints)\n * - Query parameter token (for GET endpoints)\n *\n * @example\n * ```typescript\n * app.post(\"/api/endpoint\", async (c) => {\n * const payload = getValidatedPayload(c);\n * if (!payload) {\n * return c.json({ error: \"Invalid token\" }, 401);\n * }\n * // Use payload data\n * });\n * ```\n */\nexport function getValidatedPayload(c: Context): XMTPToolsPayload | null {\n\t// Try Authorization header first (for POST endpoints)\n\tconst authHeader = c.req.header(\"Authorization\")\n\tif (authHeader?.startsWith(\"Bearer \")) {\n\t\tconst token = authHeader.substring(7) // Remove \"Bearer \" prefix\n\t\treturn validateXMTPToolsToken(token)\n\t}\n\n\t// Fall back to query parameter (for GET endpoints)\n\tconst token = c.req.query(\"token\")\n\tif (!token) {\n\t\treturn null\n\t}\n\n\treturn validateXMTPToolsToken(token)\n}\n\n/**\n * Gets the JWT secret for token signing, with lazy initialization\n * Uses XMTP_DB_ENCRYPTION_KEY environment variable for consistency\n * Only falls back to development secret in development/test environments\n */\nfunction getJwtSecret(): string {\n\tconst secret = process.env.XMTP_DB_ENCRYPTION_KEY\n\tconst nodeEnv = process.env.NODE_ENV || \"development\"\n\n\t// In production, require a real JWT secret\n\tif (nodeEnv === \"production\" && !secret) {\n\t\tthrow new Error(\n\t\t\t\"XMTP_DB_ENCRYPTION_KEY environment variable is required in production. \" +\n\t\t\t\t\"Generate a secure random secret for JWT token signing.\"\n\t\t)\n\t}\n\n\t// In development/test, allow fallback but warn only when actually used\n\tif (!secret) {\n\t\tlogger.warn(\n\t\t\t\"โ ๏ธ [SECURITY] Using fallback JWT secret for development. \" +\n\t\t\t\t\"Set XMTP_DB_ENCRYPTION_KEY environment variable for production.\"\n\t\t)\n\t\treturn \"fallback-secret-for-dev-only\"\n\t}\n\n\treturn secret\n}\n\n/**\n * Gets the API key for authentication, with lazy initialization\n * Requires XMTP_API_KEY environment variable in production\n * Only falls back to development key in development/test environments\n */\nfunction getApiKey(): string {\n\tconst apiKey = process.env.XMTP_API_KEY\n\tconst nodeEnv = process.env.NODE_ENV || \"development\"\n\n\t// In production, require a real API key\n\tif (nodeEnv === \"production\" && !apiKey) {\n\t\tthrow new Error(\n\t\t\t\"XMTP_API_KEY environment variable is required in production. \" +\n\t\t\t\t\"Generate a secure random API key for authentication.\"\n\t\t)\n\t}\n\n\t// In development/test, allow fallback but warn only when actually used\n\tif (!apiKey) {\n\t\tlogger.warn(\n\t\t\t\"โ ๏ธ [SECURITY] Using fallback API key for development. \" +\n\t\t\t\t\"Set XMTP_API_KEY environment variable for production.\"\n\t\t)\n\t\treturn \"fallback-api-key-for-dev-only\"\n\t}\n\n\treturn apiKey\n}\n\n/**\n * JWT token expiry time in seconds (5 minutes)\n */\nconst JWT_EXPIRY = 5 * 60 // 5 minutes in seconds\n\n/**\n * Generates a signed JWT token for XMTP tools authentication\n *\n * @param {Omit<XMTPToolsPayload, \"issued\" | \"expires\">} payload - Token payload without timestamp fields\n * @returns {string} Signed JWT token\n *\n * @description\n * Creates a JWT token with automatic timestamp fields:\n * - issued: Current timestamp\n * - expires: Current timestamp + JWT_EXPIRY\n *\n * @example\n * ```typescript\n * const token = generateXMTPToolsToken({\n * action: \"send\",\n * conversationId: \"0x123...\"\n * });\n * ```\n */\nexport function generateXMTPToolsToken(\n\tpayload: Omit<XMTPToolsPayload, \"issued\" | \"expires\">\n): string {\n\tconst startTime = performance.now()\n\tlogger.debug(\"๐ [JWT] Starting token generation...\")\n\n\tconst now = Math.floor(Date.now() / 1000)\n\tconst fullPayload: XMTPToolsPayload = {\n\t\t...payload,\n\t\tissued: now,\n\t\texpires: now + JWT_EXPIRY\n\t}\n\n\tconst token = jwt.sign(fullPayload, getJwtSecret(), {\n\t\texpiresIn: JWT_EXPIRY\n\t})\n\n\tconst endTime = performance.now()\n\tlogger.debug(\n\t\t`๐ [JWT] Token generation completed in ${(endTime - startTime).toFixed(2)}ms`\n\t)\n\n\treturn token\n}\n\n/**\n * Validates an XMTP tools token using either API key or JWT verification\n *\n * @param {string} token - Token to validate (either API key or JWT)\n * @returns {XMTPToolsPayload | null} Validated payload or null if invalid\n *\n * @description\n * Supports two authentication methods in order of precedence:\n * 1. API key authentication - Direct comparison with XMTP_API_KEY\n * 2. JWT token authentication - Signature verification and expiry check\n *\n * For API key authentication, returns a default payload with 1-hour expiry.\n * For JWT authentication, validates signature and checks expiry timestamp.\n *\n * @example\n * ```typescript\n * const payload = validateXMTPToolsToken(userToken);\n * if (payload) {\n * console.log(`Action: ${payload.action}`);\n * console.log(`Conversation: ${payload.conversationId}`);\n * }\n * ```\n */\nexport function validateXMTPToolsToken(token: string): XMTPToolsPayload | null {\n\tconst startTime = performance.now()\n\tlogger.debug(\"๐ [JWT] Starting token validation...\")\n\n\t// First try API key authentication\n\tif (token === getApiKey()) {\n\t\tlogger.debug(\"๐ [Auth] Using API key authentication\")\n\t\t// Return a valid payload for API key auth\n\t\tconst now = Math.floor(Date.now() / 1000)\n\t\tconst result = {\n\t\t\taction: \"send\" as const, // Default action\n\t\t\tconversationId: \"\", // Will be filled by endpoint\n\t\t\tissued: now,\n\t\t\texpires: now + 3600 // API keys are valid for 1 hour\n\t\t}\n\n\t\tconst endTime = performance.now()\n\t\tlogger.debug(\n\t\t\t`๐ [JWT] API key validation completed in ${(endTime - startTime).toFixed(2)}ms`\n\t\t)\n\t\treturn result\n\t}\n\n\t// Then try JWT token authentication\n\ttry {\n\t\tconst decoded = jwt.verify(token, getJwtSecret()) as XMTPToolsPayload\n\t\tlogger.debug(\"๐ [Auth] Using JWT token authentication\")\n\n\t\t// Additional expiry check\n\t\tconst now = Math.floor(Date.now() / 1000)\n\t\tif (decoded.expires < now) {\n\t\t\tconsole.log(\"๐ XMTP tools token has expired\")\n\t\t\tconst endTime = performance.now()\n\t\t\tlogger.debug(\n\t\t\t\t`๐ [JWT] Token validation failed (expired) in ${(endTime - startTime).toFixed(2)}ms`\n\t\t\t)\n\t\t\treturn null\n\t\t}\n\n\t\tconst endTime = performance.now()\n\t\tlogger.debug(\n\t\t\t`๐ [JWT] JWT validation completed in ${(endTime - startTime).toFixed(2)}ms`\n\t\t)\n\t\treturn decoded\n\t} catch (error) {\n\t\tlogger.error(\n\t\t\t\"๐ Invalid XMTP tools token and not matching API key:\",\n\t\t\terror\n\t\t)\n\t\tconst endTime = performance.now()\n\t\tlogger.debug(\n\t\t\t`๐ [JWT] Token validation failed in ${(endTime - startTime).toFixed(2)}ms`\n\t\t)\n\t\treturn null\n\t}\n}\n"],"mappings":"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","names":["createSigner","createUser","Client","Client","identifier","address","createSigner","createUser","logger","logger","createUser","createSigner","behaviorContext","logger","logger","logger","Client","IdentifierKind","ReplyCodec"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/client.ts","../scripts/revoke-installations.ts","../src/lib/secret.ts","../src/plugin.ts","../src/lib/jwt.ts"],"sourcesContent":["export {\n\tAgent,\n\tcreateSigner,\n\tcreateUser,\n\tfilter,\n\tgetTestUrl\n} from \"@xmtp/agent-sdk\"\n\nexport type * from \"./types\"\n\nexport {\n\tDEFAULT_AMOUNT,\n\tDEFAULT_OPTIONS,\n\tMAX_USDC_AMOUNT\n} from \"./constants\"\n// NodeNext/Node16 requires explicit extensions for relative imports\n// NodeNext/Node16 requires explicit extensions for relative imports\n\n// ===================================================================\n// XMTP Client and Connection Management\n// ===================================================================\nexport {\n\tcreateXMTPClient,\n\tcreateSigner as createXMTPSigner,\n\tgetDbPath,\n\tlogAgentDetails,\n\tvalidateEnvironment,\n\tXMTPConnectionManager,\n\tderiveAgentSecret,\n\tresolveAgentSecret\n} from \"./client\"\nexport type { XMTPConnectionConfig } from \"./client\"\n\n// ===================================================================\n// XMTP Plugin for Agent Integration\n// ===================================================================\nexport { XMTPPlugin } from \"./plugin\"\nexport type { Plugin } from \"./plugin\"\n\n// ===================================================================\n// JWT Utilities for XMTP Tools\n// ===================================================================\nexport { generateXMTPToolsToken } from \"./lib/jwt\"\nexport type { XMTPToolsPayload } from \"./lib/jwt\"\n\n// ===================================================================\n// XMTP Core SDK Exports\n// ===================================================================\nexport {\n\tClient,\n\tIdentifierKind,\n\t// type Conversation,\n\ttype DecodedMessage,\n\ttype Dm,\n\t// type Group,\n\ttype LogLevel,\n\ttype Signer,\n\ttype XmtpEnv\n} from \"@xmtp/node-sdk\"\n\n// ===================================================================\n// XMTP Content Types\n// ===================================================================\nexport {\n\tContentTypeTransactionReference,\n\ttype TransactionReference\n} from \"@xmtp/content-type-transaction-reference\"\n\nexport { ContentTypeText, type TextParameters } from \"@xmtp/content-type-text\"\n\nexport {\n\tContentTypeReaction,\n\ttype Reaction\n} from \"@xmtp/content-type-reaction\"\n\nexport {\n\tContentTypeReply,\n\tReplyCodec,\n\ttype Reply\n} from \"@xmtp/content-type-reply\"\n\nexport {\n\tContentTypeGroupUpdated,\n\tGroupUpdatedCodec,\n\ttype GroupUpdated\n} from \"@xmtp/content-type-group-updated\"\n\nexport {\n\tContentTypeWalletSendCalls,\n\ttype WalletSendCallsParams\n} from \"@xmtp/content-type-wallet-send-calls\"\n","// ===================================================================\n// Betting Configuration\n// ===================================================================\nexport const DEFAULT_OPTIONS = [\"yes\", \"no\"]\nexport const DEFAULT_AMOUNT = \"0.1\"\nexport const MAX_USDC_AMOUNT = 10 // Maximum allowed USDC transaction amount\n","import { getRandomValues } from \"node:crypto\"\nimport fs from \"node:fs\"\nimport path from \"node:path\"\nimport { logger } from \"@hybrd/utils\"\nimport { ReactionCodec } from \"@xmtp/content-type-reaction\"\nimport { ReplyCodec } from \"@xmtp/content-type-reply\"\nimport { TransactionReferenceCodec } from \"@xmtp/content-type-transaction-reference\"\nimport { WalletSendCallsCodec } from \"@xmtp/content-type-wallet-send-calls\"\nimport { Client, IdentifierKind, type Signer, XmtpEnv } from \"@xmtp/node-sdk\"\nimport { fromString, toString as uint8arraysToString } from \"uint8arrays\"\nimport { http, createWalletClient, toBytes } from \"viem\"\nimport { privateKeyToAccount } from \"viem/accounts\"\nimport { sepolia } from \"viem/chains\"\nimport { revokeOldInstallations } from \"../scripts/revoke-installations\"\nimport { resolveAgentSecret } from \"./lib/secret.js\"\nimport { XmtpClient } from \"./types\"\n\nexport { deriveAgentSecret, resolveAgentSecret } from \"./lib/secret.js\"\n\n// ===================================================================\n// Type Definitions\n// ===================================================================\ninterface User {\n\tkey: `0x${string}`\n\taccount: ReturnType<typeof privateKeyToAccount>\n\twallet: any // Simplified to avoid deep type instantiation\n}\n\n// ===================================================================\n// User and Signer Creation\n// ===================================================================\nexport const createUser = (key: string): User => {\n\tconst account = privateKeyToAccount(key as `0x${string}`)\n\treturn {\n\t\tkey: key as `0x${string}`,\n\t\taccount,\n\t\twallet: createWalletClient({\n\t\t\taccount,\n\t\t\tchain: sepolia,\n\t\t\ttransport: http()\n\t\t})\n\t}\n}\n\nexport const createSigner = (key: string): Signer => {\n\tif (!key || typeof key !== \"string\") {\n\t\tthrow new Error(\"XMTP wallet key must be a non-empty string\")\n\t}\n\tconst sanitizedKey = key.startsWith(\"0x\") ? key : `0x${key}`\n\tconst user = createUser(sanitizedKey)\n\treturn {\n\t\ttype: \"EOA\",\n\t\tgetIdentifier: () => ({\n\t\t\tidentifierKind: 0 as IdentifierKind.Ethereum, // Use numeric value to avoid ambient const enum issue\n\t\t\tidentifier: user.account.address.toLowerCase()\n\t\t}),\n\t\tsignMessage: async (message: string) => {\n\t\t\tconst signature = await user.wallet.signMessage({\n\t\t\t\tmessage,\n\t\t\t\taccount: user.account\n\t\t\t})\n\t\t\treturn toBytes(signature)\n\t\t}\n\t}\n}\n\n// XMTP XmtpClient setup\n// const xmtpClient: XmtpClient | null = null\n\n// Function to clear XMTP database when hitting installation limits\nasync function clearXMTPDatabase(address: string, env: string) {\n\tlogger.debug(\"๐งน Clearing XMTP database to resolve installation limit...\")\n\n\t// Get the storage directory using the same logic as getDbPath\n\tconst getStorageDirectory = () => {\n\t\tconst customStoragePath = process.env.XMTP_STORAGE_PATH\n\n\t\tif (customStoragePath) {\n\t\t\treturn path.isAbsolute(customStoragePath)\n\t\t\t\t? customStoragePath\n\t\t\t\t: path.resolve(process.cwd(), customStoragePath)\n\t\t}\n\n\t\t// Default to .hybrid/.xmtp in current working directory\n\t\treturn path.join(process.cwd(), \".hybrid\", \".xmtp\")\n\t}\n\n\t// Clear local database files\n\tconst dbPattern = `${env}-${address}.db3`\n\tconst storageDir = getStorageDirectory()\n\n\t// Primary storage directory\n\tconst possiblePaths = [\n\t\tstorageDir,\n\t\t// Legacy fallback paths for backward compatibility\n\t\tpath.join(process.cwd(), \".data\", \"xmtp\"),\n\t\tpath.join(process.cwd(), \"..\", \".data\", \"xmtp\"),\n\t\tpath.join(process.cwd(), \"..\", \"..\", \".data\", \"xmtp\"),\n\t\t// Monorepo root fallback\n\t\tpath.join(process.cwd(), \"..\", \"..\", \".data\", \"xmtp\")\n\t]\n\n\tfor (const dir of possiblePaths) {\n\t\ttry {\n\t\t\tif (fs.existsSync(dir)) {\n\t\t\t\tconst files = fs.readdirSync(dir)\n\t\t\t\tconst matchingFiles = files.filter(\n\t\t\t\t\t(file) =>\n\t\t\t\t\t\tfile.includes(dbPattern) ||\n\t\t\t\t\t\tfile.includes(address) ||\n\t\t\t\t\t\tfile.includes(`xmtp-${env}-${address}`)\n\t\t\t\t)\n\n\t\t\t\tfor (const file of matchingFiles) {\n\t\t\t\t\tconst fullPath = path.join(dir, file)\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfs.unlinkSync(fullPath)\n\t\t\t\t\t\tlogger.debug(`โ
Removed: ${fullPath}`)\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tlogger.debug(`โ ๏ธ Could not remove ${fullPath}:`, err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (err) {\n\t\t\t// Ignore errors when checking directories\n\t\t}\n\t}\n}\n\nexport async function createXMTPClient(\n\tprivateKey: string,\n\topts?: {\n\t\tpersist?: boolean\n\t\tmaxRetries?: number\n\t\tstoragePath?: string\n\t}\n): Promise<XmtpClient> {\n\tconst { persist = true, maxRetries = 3, storagePath } = opts ?? {}\n\tlet attempt = 0\n\n\t// Extract common variables for error handling\n\t// const actualSigner = signer\n\tconst signer = createSigner(privateKey)\n\n\tif (!signer) {\n\t\tthrow new Error(\n\t\t\t\"No signer provided and AGENT_WALLET_KEY environment variable is not set\"\n\t\t)\n\t}\n\n\tconst { XMTP_ENV } = process.env\n\tconst agentSecret = resolveAgentSecret(privateKey)\n\n\t// Get the wallet address to use the correct database\n\tconst identifier = await signer.getIdentifier()\n\tconst address = identifier.identifier\n\n\twhile (attempt < maxRetries) {\n\t\ttry {\n\t\t\tlogger.debug(\n\t\t\t\t`๐ Attempt ${attempt + 1}/${maxRetries} to create XMTP client...`\n\t\t\t)\n\n\t\t\t// Always require encryption key and persistence - no stateless mode\n\t\t\tif (!persist) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Stateless mode is not supported. XMTP client must run in persistent mode \" +\n\t\t\t\t\t\t\"to properly receive and process messages. Set persist: true or remove the persist option \" +\n\t\t\t\t\t\t\"to use the default persistent mode.\"\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst dbEncryptionKey = getEncryptionKeyFromHex(agentSecret)\n\t\t\tconst dbPath = await getDbPath(\n\t\t\t\t`${XMTP_ENV || \"dev\"}-${address}`,\n\t\t\t\tstoragePath\n\t\t\t)\n\t\t\tlogger.debug(`๐ Using database path: ${dbPath}`)\n\n\t\t\t// Always create a fresh client and sync it\n\t\t\tconst client = await Client.create(signer, {\n\t\t\t\tdbEncryptionKey,\n\t\t\t\tenv: XMTP_ENV as XmtpEnv,\n\t\t\t\tdbPath,\n\t\t\t\tcodecs: [\n\t\t\t\t\tnew ReplyCodec(),\n\t\t\t\t\tnew ReactionCodec(),\n\t\t\t\t\tnew WalletSendCallsCodec(),\n\t\t\t\t\tnew TransactionReferenceCodec()\n\t\t\t\t]\n\t\t\t})\n\n\t\t\t// Force sync conversations to ensure we have the latest data\n\t\t\tlogger.debug(\"๐ก Syncing conversations to ensure latest state...\")\n\t\t\tawait client.conversations.sync()\n\n\t\t\tawait backupDbToPersistentStorage(\n\t\t\t\tdbPath,\n\t\t\t\t`${XMTP_ENV || \"dev\"}-${address}`\n\t\t\t)\n\n\t\t\tconsole.log(`Wallet: ${address}`)\n\t\t\tconsole.log(`Env: ${XMTP_ENV || \"dev\"}`)\n\t\t\tconsole.log(`Storage: persistent`)\n\n\t\t\treturn client as unknown as XmtpClient\n\t\t} catch (error) {\n\t\t\tattempt++\n\n\t\t\tif (\n\t\t\t\terror instanceof Error &&\n\t\t\t\t(error.message.includes(\"installations\") ||\n\t\t\t\t\terror.message.match(/\\d+\\/\\d+\\s+installations/))\n\t\t\t) {\n\t\t\t\tconsole.log(\n\t\t\t\t\t`๐ฅ Installation limit reached (attempt ${attempt}/${maxRetries})`\n\t\t\t\t)\n\n\t\t\t\tif (attempt < maxRetries) {\n\t\t\t\t\t// Get wallet address for database clearing\n\t\t\t\t\tconst identifier = await signer.getIdentifier()\n\t\t\t\t\tconst address = identifier.identifier\n\n\t\t\t\t\t// Extract inboxId from the error message\n\t\t\t\t\tconst inboxIdMatch = error.message.match(/InboxID ([a-f0-9]+)/)\n\t\t\t\t\tconst inboxId = inboxIdMatch ? inboxIdMatch[1] : undefined\n\n\t\t\t\t\t// First try to revoke old installations\n\t\t\t\t\tconst revocationSuccess = await revokeOldInstallations(\n\t\t\t\t\t\tsigner,\n\t\t\t\t\t\tinboxId\n\t\t\t\t\t)\n\n\t\t\t\t\tif (revocationSuccess) {\n\t\t\t\t\t\tconsole.log(\"๐ฏ Installations revoked, retrying connection...\")\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\"โ ๏ธ Installation revocation failed or not needed, clearing database...\"\n\t\t\t\t\t\t)\n\t\t\t\t\t\t// Clear database as fallback\n\t\t\t\t\t\tawait clearXMTPDatabase(address, process.env.XMTP_ENV || \"dev\")\n\t\t\t\t\t}\n\n\t\t\t\t\t// Wait a bit before retrying\n\t\t\t\t\tconst delay = Math.pow(2, attempt) * 1000 // Exponential backoff\n\t\t\t\t\tconsole.log(`โณ Waiting ${delay}ms before retry...`)\n\t\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay))\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\"โ Failed to resolve installation limit after all retries\"\n\t\t\t\t\t)\n\t\t\t\t\tconsole.error(\"๐ก Possible solutions:\")\n\t\t\t\t\tconsole.error(\" 1. Use a different wallet (generate new keys)\")\n\t\t\t\t\tconsole.error(\" 2. Switch XMTP environments (dev <-> production)\")\n\t\t\t\t\tconsole.error(\" 3. Wait and try again later\")\n\t\t\t\t\tconsole.error(\" 4. Contact XMTP support for manual intervention\")\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t} else if (\n\t\t\t\terror instanceof Error &&\n\t\t\t\terror.message.includes(\"Association error: Missing identity update\")\n\t\t\t) {\n\t\t\t\tconsole.log(\n\t\t\t\t\t`๐ Identity association error detected (attempt ${attempt}/${maxRetries})`\n\t\t\t\t)\n\n\t\t\t\tif (attempt < maxRetries) {\n\t\t\t\t\tconsole.log(\"๐ง Attempting automatic identity refresh...\")\n\n\t\t\t\t\t// Try to refresh identity by creating a persistent client first\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconsole.log(\"๐ Creating persistent client to refresh identity...\")\n\t\t\t\t\t\tconst tempEncryptionKey = getEncryptionKeyFromHex(agentSecret)\n\t\t\t\t\t\tconst tempClient = await Client.create(signer, {\n\t\t\t\t\t\t\tdbEncryptionKey: tempEncryptionKey,\n\t\t\t\t\t\t\tenv: XMTP_ENV as XmtpEnv,\n\t\t\t\t\t\t\tdbPath: await getDbPath(\n\t\t\t\t\t\t\t\t`${XMTP_ENV || \"dev\"}-${address}`,\n\t\t\t\t\t\t\t\tstoragePath\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tcodecs: [\n\t\t\t\t\t\t\t\tnew ReplyCodec(),\n\t\t\t\t\t\t\t\tnew ReactionCodec(),\n\t\t\t\t\t\t\t\tnew WalletSendCallsCodec(),\n\t\t\t\t\t\t\t\tnew TransactionReferenceCodec()\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\tconsole.log(\"๐ก Syncing identity and conversations...\")\n\t\t\t\t\t\tawait tempClient.conversations.sync()\n\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\"โ
Identity refresh successful, retrying original request...\"\n\t\t\t\t\t\t)\n\n\t\t\t\t\t\t// Wait a bit before retrying\n\t\t\t\t\t\tconst delay = Math.pow(2, attempt) * 1000 // Exponential backoff\n\t\t\t\t\t\tconsole.log(`โณ Waiting ${delay}ms before retry...`)\n\t\t\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay))\n\t\t\t\t\t} catch (refreshError) {\n\t\t\t\t\t\tconsole.log(`โ Identity refresh failed:`, refreshError)\n\t\t\t\t\t\t// Continue to the retry logic\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\"โ Failed to resolve identity association error after all retries\"\n\t\t\t\t\t)\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\"๐ก Try running: pnpm with-env pnpm --filter @hybrd/xmtp refresh:identity\"\n\t\t\t\t\t)\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// For other errors, don't retry\n\t\t\t\tthrow error\n\t\t\t}\n\t\t}\n\t}\n\n\tthrow new Error(\"Max retries exceeded\")\n}\n\n// ===================================================================\n// Encryption Key Management\n// ===================================================================\n/**\n * Generate a random encryption key\n * @returns The encryption key as a hex string\n */\nexport const generateEncryptionKeyHex = () => {\n\tconst uint8Array = getRandomValues(new Uint8Array(32))\n\treturn uint8arraysToString(uint8Array, \"hex\")\n}\n\n/**\n * Get the encryption key from a hex string\n * @param hex - The hex string\n * @returns The encryption key as Uint8Array\n */\nconst getEncryptionKeyFromHex = (hex: string): Uint8Array => {\n\treturn fromString(hex, \"hex\")\n}\n\n// ===================================================================\n// Database Path Management\n// ===================================================================\nexport const getDbPath = async (description = \"xmtp\", storagePath?: string) => {\n\t// Allow custom storage path via environment variable\n\tconst customStoragePath = process.env.XMTP_STORAGE_PATH\n\n\tlet volumePath: string\n\n\tif (customStoragePath) {\n\t\t// Use custom storage path if provided\n\t\tvolumePath = path.isAbsolute(customStoragePath)\n\t\t\t? customStoragePath\n\t\t\t: path.resolve(process.cwd(), customStoragePath)\n\t} else if (storagePath) {\n\t\tvolumePath = path.isAbsolute(storagePath)\n\t\t\t? storagePath\n\t\t\t: path.resolve(process.cwd(), storagePath)\n\t} else {\n\t\t// Default to .hybrid/.xmtp in current working directory\n\t\tvolumePath = path.join(process.cwd(), \".hybrid\", \".xmtp\")\n\t}\n\n\tconst dbPath = `${volumePath}/${description}.db3`\n\n\tif (typeof globalThis !== \"undefined\" && \"XMTP_STORAGE\" in globalThis) {\n\t\ttry {\n\t\t\tconsole.log(`๐ฆ Using Cloudflare R2 storage for: ${dbPath}`)\n\n\t\t\tconst r2Bucket = (globalThis as any).XMTP_STORAGE\n\t\t\tconst remotePath = `xmtp-databases/${description}.db3`\n\n\t\t\ttry {\n\t\t\t\tconst existingObject = await r2Bucket.head(remotePath)\n\t\t\t\tif (existingObject) {\n\t\t\t\t\tconsole.log(`๐ฅ Downloading existing database from R2 storage...`)\n\n\t\t\t\t\tif (!fs.existsSync(volumePath)) {\n\t\t\t\t\t\tfs.mkdirSync(volumePath, { recursive: true })\n\t\t\t\t\t}\n\n\t\t\t\t\tconst object = await r2Bucket.get(remotePath)\n\t\t\t\t\tif (object) {\n\t\t\t\t\t\tconst fileData = await object.arrayBuffer()\n\t\t\t\t\t\tfs.writeFileSync(dbPath, new Uint8Array(fileData))\n\t\t\t\t\t\tconsole.log(`โ
Database downloaded from R2 storage`)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(`๐ No existing database found in R2 storage`)\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.log(`โ ๏ธ Failed to download database from R2 storage:`, error)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.log(`โ ๏ธ R2 storage not available:`, error)\n\t\t}\n\t}\n\n\tif (!fs.existsSync(volumePath)) {\n\t\tfs.mkdirSync(volumePath, { recursive: true })\n\t}\n\n\treturn dbPath\n}\n\nconst backupDbToPersistentStorage = async (\n\tdbPath: string,\n\tdescription: string\n) => {\n\tif (\n\t\ttypeof globalThis !== \"undefined\" &&\n\t\t\"XMTP_STORAGE\" in globalThis &&\n\t\tfs.existsSync(dbPath)\n\t) {\n\t\ttry {\n\t\t\tconsole.log(`๐ฆ Backing up database to R2 storage: ${dbPath}`)\n\n\t\t\tconst r2Bucket = (globalThis as any).XMTP_STORAGE\n\t\t\tconst remotePath = `xmtp-databases/${description}.db3`\n\n\t\t\tconst fileData = fs.readFileSync(dbPath)\n\t\t\tawait r2Bucket.put(remotePath, fileData)\n\t\t\tconsole.log(`โ
Database backed up to R2 storage: ${remotePath}`)\n\t\t} catch (error) {\n\t\t\tconsole.log(`โ ๏ธ Failed to backup database to R2 storage:`, error)\n\t\t}\n\t}\n}\n\n// ===================================================================\n// Logging and Debugging\n// ===================================================================\nexport const logAgentDetails = async (\n\tclients: XmtpClient | XmtpClient[]\n): Promise<void> => {\n\tconst clientsByAddress = Array.isArray(clients)\n\t\t? clients.reduce<Record<string, XmtpClient[]>>((acc, XmtpClient) => {\n\t\t\t\tconst address = XmtpClient.accountIdentifier?.identifier ?? \"\"\n\t\t\t\tacc[address] = acc[address] ?? []\n\t\t\t\tacc[address].push(XmtpClient)\n\t\t\t\treturn acc\n\t\t\t}, {})\n\t\t: {\n\t\t\t\t[clients.accountIdentifier?.identifier ?? \"\"]: [clients]\n\t\t\t}\n\n\tfor (const [address, clientGroup] of Object.entries(clientsByAddress)) {\n\t\tconst firstClient = clientGroup[0]\n\t\tconst inboxId = firstClient?.inboxId\n\t\tconst environments = clientGroup\n\t\t\t.map((c) => c.options?.env ?? \"dev\")\n\t\t\t.join(\", \")\n\t\tconsole.log(`\\x1b[38;2;252;76;52m\n โโโ โโโโโโโ โโโโโโโโโโโโโโโโโโโโ \n โโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ\n โโโโโโ โโโโโโโโโโโ โโโ โโโโโโโโ\n โโโโโโ โโโโโโโโโโโ โโโ โโโโโโโ \n โโโโ โโโโโโ โโโ โโโ โโโ โโโ \n โโโ โโโโโโ โโโ โโโ โโโ \n \\x1b[0m`)\n\n\t\tconst urls = [`http://xmtp.chat/dm/${address}`]\n\n\t\tconst conversations = await firstClient?.conversations.list()\n\n\t\tconsole.log(`\n โ XMTP XmtpClient:\n โข Address: ${address}\n โข Conversations: ${conversations?.length}\n โข InboxId: ${inboxId}\n โข Networks: ${environments}\n ${urls.map((url) => `โข URL: ${url}`).join(\"\\n\")}`)\n\t}\n}\n\n// ===================================================================\n// Environment Validation\n// ===================================================================\nexport function validateEnvironment(vars: string[]): Record<string, string> {\n\tconst missing = vars.filter((v) => !process.env[v])\n\n\tif (missing.length) {\n\t\ttry {\n\t\t\tconst envPath = path.resolve(process.cwd(), \".env\")\n\t\t\tif (fs.existsSync(envPath)) {\n\t\t\t\tconst envVars = fs\n\t\t\t\t\t.readFileSync(envPath, \"utf-8\")\n\t\t\t\t\t.split(\"\\n\")\n\t\t\t\t\t.filter((line) => line.trim() && !line.startsWith(\"#\"))\n\t\t\t\t\t.reduce<Record<string, string>>((acc, line) => {\n\t\t\t\t\t\tconst [key, ...val] = line.split(\"=\")\n\t\t\t\t\t\tif (key && val.length) acc[key.trim()] = val.join(\"=\").trim()\n\t\t\t\t\t\treturn acc\n\t\t\t\t\t}, {})\n\n\t\t\t\tmissing.forEach((v) => {\n\t\t\t\t\tif (envVars[v]) process.env[v] = envVars[v]\n\t\t\t\t})\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(e)\n\t\t\t/* ignore errors */\n\t\t}\n\n\t\tconst stillMissing = vars.filter((v) => !process.env[v])\n\t\tif (stillMissing.length) {\n\t\t\tconsole.error(\"Missing env vars:\", stillMissing.join(\", \"))\n\t\t\tprocess.exit(1)\n\t\t}\n\t}\n\n\treturn vars.reduce<Record<string, string>>((acc, key) => {\n\t\tacc[key] = process.env[key] as string\n\t\treturn acc\n\t}, {})\n}\n\n/**\n * Diagnose XMTP environment and identity issues (internal use only)\n */\nasync function diagnoseXMTPIdentityIssue(\n\tclient: XmtpClient,\n\tinboxId: string,\n\tenvironment: string\n): Promise<{\n\tcanResolve: boolean\n\tsuggestions: string[]\n\tdetails: Record<string, any>\n}> {\n\tconst suggestions: string[] = []\n\tconst details: Record<string, any> = {\n\t\tenvironment,\n\t\tinboxId,\n\t\ttimestamp: new Date().toISOString()\n\t}\n\n\ttry {\n\t\t// Try to resolve the inbox state\n\t\tconst inboxState = await client.preferences.inboxStateFromInboxIds([\n\t\t\tinboxId\n\t\t])\n\n\t\tif (inboxState.length === 0) {\n\t\t\tsuggestions.push(\n\t\t\t\t`Inbox ID ${inboxId} not found in ${environment} environment`\n\t\t\t)\n\t\t\tsuggestions.push(\n\t\t\t\t\"Try switching XMTP_ENV to 'dev' if currently 'production' or vice versa\"\n\t\t\t)\n\t\t\tsuggestions.push(\n\t\t\t\t\"Verify the user has created an identity on this XMTP network\"\n\t\t\t)\n\t\t\tdetails.inboxStateFound = false\n\t\t\treturn { canResolve: false, suggestions, details }\n\t\t}\n\n\t\tconst inbox = inboxState[0]\n\t\tif (!inbox) {\n\t\t\tsuggestions.push(\"Inbox state returned empty data\")\n\t\t\tdetails.inboxStateFound = false\n\t\t\treturn { canResolve: false, suggestions, details }\n\t\t}\n\n\t\tdetails.inboxStateFound = true\n\t\tdetails.identifierCount = inbox.identifiers?.length || 0\n\n\t\tif (!inbox.identifiers || inbox.identifiers.length === 0) {\n\t\t\tsuggestions.push(\"Inbox found but has no identifiers\")\n\t\t\tsuggestions.push(\"This indicates incomplete identity registration\")\n\t\t\tsuggestions.push(\"User may need to re-register their identity on XMTP\")\n\t\t\tdetails.hasIdentifiers = false\n\t\t\treturn { canResolve: false, suggestions, details }\n\t\t}\n\n\t\t// Successfully resolved\n\t\tdetails.hasIdentifiers = true\n\t\tdetails.resolvedAddress = inbox.identifiers[0]?.identifier\n\t\treturn {\n\t\t\tcanResolve: true,\n\t\t\tsuggestions: [\"Identity resolved successfully\"],\n\t\t\tdetails\n\t\t}\n\t} catch (error) {\n\t\tconst errorMessage = error instanceof Error ? error.message : String(error)\n\t\tdetails.error = errorMessage\n\n\t\tif (errorMessage.includes(\"Association error\")) {\n\t\t\tsuggestions.push(\"XMTP identity association error detected\")\n\t\t\tsuggestions.push(\n\t\t\t\t\"Check if user exists on the correct XMTP environment (dev vs production)\"\n\t\t\t)\n\t\t\tsuggestions.push(\n\t\t\t\t\"Identity may need to be recreated on the current environment\"\n\t\t\t)\n\t\t}\n\n\t\tif (errorMessage.includes(\"Missing identity update\")) {\n\t\t\tsuggestions.push(\"Missing identity updates in XMTP network\")\n\t\t\tsuggestions.push(\"This can indicate network sync issues\")\n\t\t\tsuggestions.push(\"Wait a few minutes and retry, or recreate identity\")\n\t\t}\n\n\t\tif (errorMessage.includes(\"database\") || errorMessage.includes(\"storage\")) {\n\t\t\tsuggestions.push(\"XMTP local database/storage issue\")\n\t\t\tsuggestions.push(\"Try clearing XMTP database and resyncing\")\n\t\t\tsuggestions.push(\"Check .data/xmtp directory permissions\")\n\t\t}\n\n\t\tsuggestions.push(\"Consider testing with a fresh XMTP identity\")\n\t\treturn { canResolve: false, suggestions, details }\n\t}\n}\n\n// ===================================================================\n// Enhanced Connection Management & Health Monitoring\n// ===================================================================\n\nexport interface XMTPConnectionConfig {\n\tmaxRetries?: number\n\tretryDelayMs?: number\n\thealthCheckIntervalMs?: number\n\tconnectionTimeoutMs?: number\n\treconnectOnFailure?: boolean\n}\n\nexport interface XMTPConnectionHealth {\n\tisConnected: boolean\n\tlastHealthCheck: Date\n\tconsecutiveFailures: number\n\ttotalReconnects: number\n\tavgResponseTime: number\n}\n\nexport class XMTPConnectionManager {\n\tprivate client: XmtpClient | null = null\n\tprivate privateKey: string\n\tprivate config: Required<XMTPConnectionConfig>\n\tprivate health: XMTPConnectionHealth\n\tprivate healthCheckTimer: NodeJS.Timeout | null = null\n\tprivate isReconnecting = false\n\n\tconstructor(privateKey: string, config: XMTPConnectionConfig = {}) {\n\t\tthis.privateKey = privateKey\n\t\tthis.config = {\n\t\t\tmaxRetries: config.maxRetries ?? 5,\n\t\t\tretryDelayMs: config.retryDelayMs ?? 1000,\n\t\t\thealthCheckIntervalMs: config.healthCheckIntervalMs ?? 30000,\n\t\t\tconnectionTimeoutMs: config.connectionTimeoutMs ?? 10000,\n\t\t\treconnectOnFailure: config.reconnectOnFailure ?? true\n\t\t}\n\n\t\tthis.health = {\n\t\t\tisConnected: false,\n\t\t\tlastHealthCheck: new Date(),\n\t\t\tconsecutiveFailures: 0,\n\t\t\ttotalReconnects: 0,\n\t\t\tavgResponseTime: 0\n\t\t}\n\t}\n\n\tasync connect(persist = false): Promise<XmtpClient> {\n\t\tif (this.client && this.health.isConnected) {\n\t\t\treturn this.client\n\t\t}\n\n\t\tlet attempt = 0\n\t\twhile (attempt < this.config.maxRetries) {\n\t\t\ttry {\n\t\t\t\tconsole.log(\n\t\t\t\t\t`๐ XMTP connection attempt ${attempt + 1}/${this.config.maxRetries}`\n\t\t\t\t)\n\n\t\t\t\tthis.client = await createXMTPClient(this.privateKey, { persist })\n\t\t\t\tthis.health.isConnected = true\n\t\t\t\tthis.health.consecutiveFailures = 0\n\n\t\t\t\t// Start health monitoring\n\t\t\t\tthis.startHealthMonitoring()\n\n\t\t\t\tconsole.log(\"โ
XMTP client connected successfully\")\n\t\t\t\treturn this.client\n\t\t\t} catch (error) {\n\t\t\t\tattempt++\n\t\t\t\tthis.health.consecutiveFailures++\n\n\t\t\t\tconsole.error(`โ XMTP connection attempt ${attempt} failed:`, error)\n\n\t\t\t\tif (attempt < this.config.maxRetries) {\n\t\t\t\t\tconst delay = this.config.retryDelayMs * Math.pow(2, attempt - 1)\n\t\t\t\t\tconsole.log(`โณ Retrying in ${delay}ms...`)\n\t\t\t\t\tawait this.sleep(delay)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`Failed to connect to XMTP after ${this.config.maxRetries} attempts`\n\t\t)\n\t}\n\n\t// private async createClientWithTimeout(persist: boolean): Promise<XmtpClient> {\n\t// const timeoutPromise = new Promise<never>((_, reject) => {\n\t// setTimeout(\n\t// () => reject(new Error(\"Connection timeout\")),\n\t// this.config.connectionTimeoutMs\n\t// )\n\t// })\n\n\t// const clientPromise = createXMTPClient(this.signer, { persist })\n\n\t// return Promise.race([clientPromise, timeoutPromise])\n\t// }\n\n\tprivate startHealthMonitoring(): void {\n\t\tif (this.healthCheckTimer) {\n\t\t\tclearInterval(this.healthCheckTimer)\n\t\t}\n\n\t\tthis.healthCheckTimer = setInterval(() => {\n\t\t\tthis.performHealthCheck()\n\t\t}, this.config.healthCheckIntervalMs)\n\t}\n\n\tprivate async performHealthCheck(): Promise<void> {\n\t\tif (!this.client) return\n\n\t\tconst startTime = Date.now()\n\n\t\ttry {\n\t\t\t// Simple health check: try to list conversations\n\t\t\tawait this.client.conversations.list()\n\n\t\t\tconst responseTime = Date.now() - startTime\n\t\t\tthis.health.avgResponseTime =\n\t\t\t\t(this.health.avgResponseTime + responseTime) / 2\n\t\t\tthis.health.lastHealthCheck = new Date()\n\t\t\tthis.health.consecutiveFailures = 0\n\t\t\tthis.health.isConnected = true\n\n\t\t\tconsole.log(`๐ XMTP health check passed (${responseTime}ms)`)\n\t\t} catch (error) {\n\t\t\tthis.health.consecutiveFailures++\n\t\t\tthis.health.isConnected = false\n\n\t\t\tconsole.error(`๐ XMTP health check failed:`, error)\n\n\t\t\t// Trigger reconnection if enabled\n\t\t\tif (this.config.reconnectOnFailure && !this.isReconnecting) {\n\t\t\t\tthis.handleConnectionFailure()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async handleConnectionFailure(): Promise<void> {\n\t\tif (this.isReconnecting) return\n\n\t\tthis.isReconnecting = true\n\t\tthis.health.totalReconnects++\n\n\t\tconsole.log(\"๐ XMTP connection lost, attempting to reconnect...\")\n\n\t\ttry {\n\t\t\tthis.client = null\n\t\t\tawait this.connect()\n\t\t\tconsole.log(\"โ
XMTP reconnection successful\")\n\t\t} catch (error) {\n\t\t\tconsole.error(\"โ XMTP reconnection failed:\", error)\n\t\t} finally {\n\t\t\tthis.isReconnecting = false\n\t\t}\n\t}\n\n\tprivate sleep(ms: number): Promise<void> {\n\t\treturn new Promise((resolve) => setTimeout(resolve, ms))\n\t}\n\n\tgetHealth(): XMTPConnectionHealth {\n\t\treturn { ...this.health }\n\t}\n\n\tgetClient(): XmtpClient | null {\n\t\treturn this.client\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tif (this.healthCheckTimer) {\n\t\t\tclearInterval(this.healthCheckTimer)\n\t\t\tthis.healthCheckTimer = null\n\t\t}\n\n\t\tthis.client = null\n\t\tthis.health.isConnected = false\n\t\tconsole.log(\"๐ XMTP client disconnected\")\n\t}\n}\n\n// Enhanced client creation with connection management\nexport async function createXMTPConnectionManager(\n\tprivateKey: string,\n\tconfig?: XMTPConnectionConfig\n): Promise<XMTPConnectionManager> {\n\tconst manager = new XMTPConnectionManager(privateKey, config)\n\tawait manager.connect()\n\treturn manager\n}\n","import { Client, type Signer } from \"@xmtp/node-sdk\"\nimport { createSigner } from \"../src/client\"\n\n// Function to revoke old installations when hitting the limit\nexport async function revokeOldInstallations(signer: Signer, inboxId?: string) {\n\tconsole.log(\"๐ง Attempting to revoke old installations...\")\n\n\ttry {\n\t\t// If we don't have the inboxId, we need to extract it from a temporary client attempt\n\t\tif (!inboxId) {\n\t\t\tconsole.log(\"โน๏ธ No inboxId provided, cannot revoke installations\")\n\t\t\treturn false\n\t\t}\n\n\t\tconst inboxStates = await Client.inboxStateFromInboxIds(\n\t\t\t[inboxId],\n\t\t\tprocess.env.XMTP_ENV as \"dev\" | \"production\"\n\t\t)\n\n\t\tif (!inboxStates[0]) {\n\t\t\tconsole.log(\"โ No inbox state found for the provided inboxId\")\n\t\t\treturn false\n\t\t}\n\n\t\tconst toRevokeInstallationBytes = inboxStates[0].installations.map(\n\t\t\t(i: { bytes: Uint8Array }) => i.bytes\n\t\t)\n\n\t\tawait Client.revokeInstallations(\n\t\t\tsigner,\n\t\t\tinboxId,\n\t\t\ttoRevokeInstallationBytes,\n\t\t\tprocess.env.XMTP_ENV as \"dev\" | \"production\"\n\t\t)\n\n\t\tconst resultingStates = await Client.inboxStateFromInboxIds(\n\t\t\t[inboxId],\n\t\t\tprocess.env.XMTP_ENV as \"dev\" | \"production\"\n\t\t)\n\n\t\tconsole.log(\n\t\t\t`๐ Revoked installations: ${toRevokeInstallationBytes.length} installations`\n\t\t)\n\t\tconsole.log(\n\t\t\t`๐ Resulting state: ${resultingStates[0]?.installations.length || 0} installations`\n\t\t)\n\n\t\treturn true\n\t} catch (error) {\n\t\tconsole.error(\"โ Error during installation revocation:\", error)\n\t\treturn false\n\t}\n}\n\n// CLI script to revoke installations\nasync function main() {\n\tconst { AGENT_WALLET_KEY } = process.env\n\tconst inboxId = process.argv[2]\n\n\tif (!AGENT_WALLET_KEY) {\n\t\tconsole.error(\"โ AGENT_WALLET_KEY is required\")\n\t\tprocess.exit(1)\n\t}\n\n\tif (!inboxId) {\n\t\tconsole.error(\"โ InboxID is required as CLI argument\")\n\t\tconsole.error(\"Usage: tsx revoke-installations.ts <inboxId>\")\n\t\tprocess.exit(1)\n\t}\n\n\tconst signer = createSigner(AGENT_WALLET_KEY)\n\tconst identifier = await signer.getIdentifier()\n\tconst address = identifier.identifier\n\n\tconsole.log(`๐ Wallet Address: ${address}`)\n\tconsole.log(`๐ Inbox ID: ${inboxId}`)\n\n\t// Try to revoke installations\n\tconst success = await revokeOldInstallations(signer, inboxId)\n\n\tif (success) {\n\t\tconsole.log(\"โ
Successfully revoked installations\")\n\t} else {\n\t\tconsole.log(\"โ Failed to revoke installations\")\n\t\tprocess.exit(1)\n\t}\n}\n\n// Run if called directly (ESM only)\ntry {\n\tif (import.meta.url === `file://${process.argv[1]}`) {\n\t\tmain().catch((error) => {\n\t\t\tconsole.error(\"๐ฅ Fatal error:\", error)\n\t\t\tprocess.exit(1)\n\t\t})\n\t}\n} catch {\n\t// import.meta not available in CJS - script not run directly\n}\n","import { toBytes } from \"viem\"\nimport { HDKey } from \"viem/accounts\"\n\n/**\n * Derives a deterministic 32-byte secret from an agent wallet private key.\n *\n * Uses BIP-32 HD key derivation at path m/44'/60'/0'/0/41 (coin type 60 = ETH,\n * index 41 is arbitrary but fixed โ chosen to avoid collision with standard\n * account derivation paths). The child private key is used directly as the\n * 32-byte secret, returned as a 64-character hex string.\n */\nexport function deriveAgentSecret(walletKey: string): string {\n\tconst keyBytes = toBytes(walletKey as `0x${string}`)\n\tconst hdKey = HDKey.fromMasterSeed(keyBytes)\n\tconst child = hdKey.derive(\"m/44'/60'/0'/0/41\")\n\tif (!child.privateKey) {\n\t\tthrow new Error(\"Failed to derive child key from wallet key\")\n\t}\n\treturn Buffer.from(child.privateKey).toString(\"hex\")\n}\n\n/**\n * Resolves the database encryption secret by deriving it from the provided wallet key.\n *\n * The wallet key must be passed explicitly โ this function does not\n * read from environment variables or external stores.\n */\nexport function resolveAgentSecret(walletKey: string): string {\n\tif (!walletKey) {\n\t\tthrow new Error(\"walletKey is required to derive the encryption secret\")\n\t}\n\treturn deriveAgentSecret(walletKey)\n}\n","import {\n\tAgent as XmtpAgent,\n\tXmtpEnv,\n\tcreateSigner,\n\tcreateUser\n} from \"@xmtp/agent-sdk\"\n\nimport { randomUUID } from \"node:crypto\"\nimport type {\n\tAgentMessage,\n\tAgentRuntime,\n\tBehaviorContext,\n\tBehaviorRegistry,\n\tPlugin,\n\tPluginContext,\n\tXmtpClient,\n\tXmtpConversation,\n\tXmtpMessage\n} from \"@hybrd/types\"\nimport { logger } from \"@hybrd/utils\"\nimport { createXMTPClient, getDbPath } from \"./client\"\nimport { ContentTypeReply, ContentTypeText, type Reply } from \"./index\"\n\n// Re-export types from @hybrd/types for backward compatibility\nexport type { Plugin }\n\n/**\n * Send a response with threading support\n */\nasync function sendResponse(\n\tconversation: XmtpConversation,\n\ttext: string,\n\toriginalMessageId: string,\n\tbehaviorContext?: BehaviorContext\n) {\n\tconst shouldThread = behaviorContext?.sendOptions?.threaded ?? false\n\n\tif (shouldThread) {\n\t\t// Send as a reply to the original message\n\t\ttry {\n\t\t\tconst reply: Reply = {\n\t\t\t\treference: originalMessageId,\n\t\t\t\tcontentType: ContentTypeText,\n\t\t\t\tcontent: text\n\t\t\t}\n\t\t\tawait conversation.send(reply, ContentTypeReply)\n\t\t\tlogger.debug(\n\t\t\t\t`โ
[sendResponse] Threaded reply sent successfully to message ${originalMessageId}`\n\t\t\t)\n\t\t} catch (error) {\n\t\t\tlogger.error(\n\t\t\t\t`โ [sendResponse] Failed to send threaded reply to message ${originalMessageId}:`,\n\t\t\t\terror\n\t\t\t)\n\t\t\t// Fall back to regular message if threaded reply fails\n\t\t\tlogger.debug(`๐ [sendResponse] Falling back to regular message`)\n\t\t\tawait conversation.send(text)\n\t\t}\n\t} else {\n\t\t// Send as a regular message\n\t\tawait conversation.send(text)\n\t}\n}\n\n/**\n * XMTP Plugin that provides XMTP functionality to the agent\n *\n * @description\n * This plugin integrates XMTP messaging capabilities into the agent's\n * HTTP server. It mounts the XMTP endpoints for handling XMTP tools requests.\n */\nexport function XMTPPlugin(): Plugin<PluginContext> {\n\treturn {\n\t\tname: \"xmtp\",\n\t\tdescription: \"Provides XMTP messaging functionality\",\n\t\tapply: async (app, context): Promise<void> => {\n\t\t\tconst { AGENT_WALLET_KEY, XMTP_ENV = \"production\" } = process.env\n\n\t\t\tconst { agent } = context\n\t\t\tconst pluginContext = context as PluginContext & {\n\t\t\t\tbehaviors?: BehaviorRegistry\n\t\t\t}\n\n\t\t\tif (!AGENT_WALLET_KEY) {\n\t\t\t\tthrow new Error(\"AGENT_WALLET_KEY must be set\")\n\t\t\t}\n\n\t\t\tconst user = createUser(AGENT_WALLET_KEY as `0x${string}`)\n\t\t\tconst signer = createSigner(user)\n\n\t\t\tconst xmtpClient = await createXMTPClient(\n\t\t\t\tAGENT_WALLET_KEY as `0x${string}`\n\t\t\t)\n\n\t\t\tconst address = user.account.address.toLowerCase()\n\t\t\tconst agentDbPath = await getDbPath(\n\t\t\t\t`agent-${XMTP_ENV || \"dev\"}-${address}`\n\t\t\t)\n\t\t\tlogger.debug(`๐ Using database path: ${agentDbPath}`)\n\n\t\t\tconst xmtp = (await XmtpAgent.create(signer, {\n\t\t\t\tenv: XMTP_ENV as XmtpEnv,\n\t\t\t\tdbPath: agentDbPath\n\t\t\t})) as any\n\n\t\t\tconst botInboxId = xmtp.client?.inboxId\n\t\t\tconst MAX_HISTORY = 20\n\n\t\t\txmtp.on(\"reaction\", async ({ conversation, message }: any) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst text = message.content.content\n\t\t\t\t\tconst messages: AgentMessage[] = [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: randomUUID(),\n\t\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\t\tparts: [{ type: \"text\", text }]\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\n\t\t\t\t\tconst baseRuntime: AgentRuntime = {\n\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\txmtpClient,\n\t\t\t\t\t\t...(context.scheduler ? { scheduler: context.scheduler } : {})\n\t\t\t\t\t}\n\n\t\t\t\t\tconst runtime = await agent.createRuntimeContext(baseRuntime)\n\n\t\t\t\t\t// Execute pre-response behaviors\n\t\t\t\t\tlet behaviorContext: BehaviorContext | undefined\n\t\t\t\t\tif (context.behaviors) {\n\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait context.behaviors.executeBefore(behaviorContext)\n\n\t\t\t\t\t\t// Check if behaviors were stopped early (e.g., due to filtering)\n\t\t\t\t\t\tif (behaviorContext.stopped) {\n\t\t\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t\t\t`๐ [XMTP Plugin] Skipping reaction response due to behavior chain being stopped`\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Check if message was filtered out by filterMessages behavior\n\t\t\t\t\t\tif (behaviorContext.sendOptions?.filtered) {\n\t\t\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t\t\t`๐ [XMTP Plugin] Skipping reaction response due to message being filtered`\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { text: reply } = await agent.generate(messages, { runtime })\n\n\t\t\t\t\t// Execute post-response behaviors\n\t\t\t\t\tif (context.behaviors) {\n\t\t\t\t\t\tif (behaviorContext) {\n\t\t\t\t\t\t\tbehaviorContext.response = reply\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\t\t\tresponse: reply\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait context.behaviors.executeAfter(behaviorContext)\n\n\t\t\t\t\t\t// Check if post behaviors were stopped early\n\t\t\t\t\t\tif (behaviorContext.stopped) {\n\t\t\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t\t\t`๐ [XMTP Plugin] Skipping reaction response due to post-behavior chain being stopped`\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Create minimal context for send options\n\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\t\tresponse: reply\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tawait sendResponse(\n\t\t\t\t\t\tconversation as unknown as XmtpConversation,\n\t\t\t\t\t\treply,\n\t\t\t\t\t\tmessage.id,\n\t\t\t\t\t\tbehaviorContext\n\t\t\t\t\t)\n\t\t\t\t} catch (err) {\n\t\t\t\t\tlogger.error(\"โ Error handling reaction:\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\txmtp.on(\"reply\", async ({ conversation, message }: any) => {\n\t\t\t\ttry {\n\t\t\t\t\t// TODO - why isn't this typed better?\n\t\t\t\t\tconst text = message.content.content as string\n\t\t\t\t\tconst messages: AgentMessage[] = [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: randomUUID(),\n\t\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\t\tparts: [{ type: \"text\", text }]\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\n\t\t\t\t\tconst baseRuntime: AgentRuntime = {\n\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\txmtpClient,\n\t\t\t\t\t\t...(context.scheduler ? { scheduler: context.scheduler } : {})\n\t\t\t\t\t}\n\n\t\t\t\t\tconst runtime = await agent.createRuntimeContext(baseRuntime)\n\n\t\t\t\t\t// Execute pre-response behaviors\n\t\t\t\t\tlet behaviorContext: BehaviorContext | undefined\n\t\t\t\t\tif (context.behaviors) {\n\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait context.behaviors.executeBefore(behaviorContext)\n\n\t\t\t\t\t\t// Check if behaviors were stopped early (e.g., due to filtering)\n\t\t\t\t\t\tif (behaviorContext.stopped) {\n\t\t\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t\t\t`๐ [XMTP Plugin] Skipping reply response due to behavior chain being stopped`\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { text: reply } = await agent.generate(messages, { runtime })\n\n\t\t\t\t\t// Execute post-response behaviors\n\t\t\t\t\tif (context.behaviors) {\n\t\t\t\t\t\tif (!behaviorContext) {\n\t\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\t\t\tresponse: reply\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tbehaviorContext.response = reply\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait context.behaviors.executeAfter(behaviorContext)\n\n\t\t\t\t\t\t// Check if post behaviors were stopped early\n\t\t\t\t\t\tif (behaviorContext.stopped) {\n\t\t\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t\t\t`๐ [XMTP Plugin] Skipping reply response due to post-behavior chain being stopped`\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Create minimal context for send options\n\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\t\tresponse: reply\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tawait sendResponse(\n\t\t\t\t\t\tconversation as unknown as XmtpConversation,\n\t\t\t\t\t\treply,\n\t\t\t\t\t\tmessage.id,\n\t\t\t\t\t\tbehaviorContext\n\t\t\t\t\t)\n\t\t\t\t} catch (err) {\n\t\t\t\t\tlogger.error(\"โ Error handling reply:\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\txmtp.on(\"text\", async ({ conversation, message }: any) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst text = message.content\n\n\t\t\t\t\tlet historyMessages: AgentMessage[] = []\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconsole.log(\"[xmtp] fetching conversation history...\")\n\t\t\t\t\t\tconst history = await conversation.messages({\n\t\t\t\t\t\t\tlimit: MAX_HISTORY + 1,\n\t\t\t\t\t\t\tdirection: 1\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t`[xmtp] got ${history.length} messages from conversation.messages()`\n\t\t\t\t\t\t)\n\n\t\t\t\t\t\tconst filtered = history\n\t\t\t\t\t\t\t.filter((msg: any) => msg.id !== message.id)\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t(msg: any) => msg.content && typeof msg.content === \"string\"\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.slice(0, MAX_HISTORY)\n\t\t\t\t\t\t\t.reverse()\n\n\t\t\t\t\t\tconsole.log(`[xmtp] after filter: ${filtered.length} messages`)\n\n\t\t\t\t\t\thistoryMessages = filtered.map((msg: any) => ({\n\t\t\t\t\t\t\tid: msg.id,\n\t\t\t\t\t\t\trole:\n\t\t\t\t\t\t\t\tmsg.senderInboxId === botInboxId\n\t\t\t\t\t\t\t\t\t? (\"assistant\" as const)\n\t\t\t\t\t\t\t\t\t: (\"user\" as const),\n\t\t\t\t\t\t\tparts: [{ type: \"text\" as const, text: msg.content as string }]\n\t\t\t\t\t\t}))\n\t\t\t\t\t} catch (historyErr) {\n\t\t\t\t\t\tconsole.error(`[xmtp] history error:`, historyErr)\n\t\t\t\t\t}\n\n\t\t\t\t\tconst messages: AgentMessage[] = [\n\t\t\t\t\t\t...historyMessages,\n\t\t\t\t\t\t{ id: randomUUID(), role: \"user\", parts: [{ type: \"text\", text }] }\n\t\t\t\t\t]\n\n\t\t\t\t\tconsole.log(`[xmtp] sending ${messages.length} messages to agent`)\n\n\t\t\t\t\tconst baseRuntime: AgentRuntime = {\n\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\txmtpClient,\n\t\t\t\t\t\t...(context.scheduler ? { scheduler: context.scheduler } : {})\n\t\t\t\t\t}\n\n\t\t\t\t\tconst runtime = await agent.createRuntimeContext(baseRuntime)\n\n\t\t\t\t\t// Execute pre-response behaviors\n\t\t\t\t\tlet behaviorContext: BehaviorContext | undefined\n\t\t\t\t\tif (context.behaviors) {\n\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait context.behaviors.executeBefore(behaviorContext)\n\n\t\t\t\t\t\t// Check if behaviors were stopped early (e.g., due to filtering)\n\t\t\t\t\t\tif (behaviorContext.stopped) {\n\t\t\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t\t\t`๐ [XMTP Plugin] Skipping text response due to behavior chain being stopped`\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { text: reply } = await agent.generate(messages, { runtime })\n\n\t\t\t\t\t// Execute post-response behaviors\n\t\t\t\t\tif (context.behaviors) {\n\t\t\t\t\t\tif (!behaviorContext) {\n\t\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\t\t\tresponse: reply\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tbehaviorContext.response = reply\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait context.behaviors.executeAfter(behaviorContext)\n\n\t\t\t\t\t\t// Check if post behaviors were stopped early\n\t\t\t\t\t\tif (behaviorContext.stopped) {\n\t\t\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t\t\t`๐ [XMTP Plugin] Skipping text response due to post-behavior chain being stopped`\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Create minimal context for send options\n\t\t\t\t\t\tbehaviorContext = {\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\tclient: xmtpClient as unknown as XmtpClient,\n\t\t\t\t\t\t\tconversation: conversation as unknown as XmtpConversation,\n\t\t\t\t\t\t\tmessage: message as unknown as XmtpMessage,\n\t\t\t\t\t\t\tresponse: reply\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tawait sendResponse(\n\t\t\t\t\t\tconversation as unknown as XmtpConversation,\n\t\t\t\t\t\treply,\n\t\t\t\t\t\tmessage.id,\n\t\t\t\t\t\tbehaviorContext\n\t\t\t\t\t)\n\t\t\t\t} catch (err) {\n\t\t\t\t\tlogger.error(\"โ Error handling text:\", err)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// Store xmtpClient in context for scheduler and other components\n\t\t\t;(context as any).xmtpClient = xmtpClient\n\n\t\t\t// Event handlers removed due to incompatibility with current XMTP agent SDK\n\n\t\t\tvoid xmtp\n\t\t\t\t.start()\n\t\t\t\t.then(() => logger.debug(\"โ
XMTP agent listener started\"))\n\t\t\t\t.catch((err: any) =>\n\t\t\t\t\tconsole.error(\"โ XMTP agent listener failed to start:\", err)\n\t\t\t\t)\n\t\t}\n\t}\n}\n","import { logger } from \"@hybrd/utils\"\nimport { Context } from \"hono\"\nimport jwt from \"jsonwebtoken\"\nimport { resolveAgentSecret } from \"./secret.js\"\n\nexport interface XMTPToolsPayload {\n\taction: \"send\" | \"reply\" | \"react\" | \"transaction\" | \"blockchain-event\"\n\tconversationId: string\n\t// Action-specific data\n\tcontent?: string\n\treferenceMessageId?: string\n\temoji?: string\n\tactionType?: \"added\" | \"removed\"\n\tfromAddress?: string\n\tchainId?: string\n\tcalls?: Array<{\n\t\tto: string\n\t\tdata: string\n\t\tmetadata?: {\n\t\t\tdescription: string\n\t\t\ttransactionType: string\n\t\t}\n\t}>\n\t// Metadata\n\tissued: number\n\texpires: number\n}\n\n/**\n * Validates token and returns payload for both GET and POST endpoints\n *\n * @param {Context} c - Hono context object containing request information\n * @returns {XMTPToolsPayload | null} The validated payload or null if invalid\n *\n * @description\n * Supports two authentication methods:\n * - Authorization header with Bearer token (for POST endpoints)\n * - Query parameter token (for GET endpoints)\n *\n * @example\n * ```typescript\n * app.post(\"/api/endpoint\", async (c) => {\n * const payload = getValidatedPayload(c);\n * if (!payload) {\n * return c.json({ error: \"Invalid token\" }, 401);\n * }\n * // Use payload data\n * });\n * ```\n */\nexport function getValidatedPayload(c: Context): XMTPToolsPayload | null {\n\t// Try Authorization header first (for POST endpoints)\n\tconst authHeader = c.req.header(\"Authorization\")\n\tif (authHeader?.startsWith(\"Bearer \")) {\n\t\tconst token = authHeader.substring(7) // Remove \"Bearer \" prefix\n\t\treturn validateXMTPToolsToken(token)\n\t}\n\n\t// Fall back to query parameter (for GET endpoints)\n\tconst token = c.req.query(\"token\")\n\tif (!token) {\n\t\treturn null\n\t}\n\n\treturn validateXMTPToolsToken(token)\n}\n\n/**\n * Gets the JWT secret for token signing, with lazy initialization\n * Derives secret from AGENT_WALLET_KEY using BIP-32\n * Only falls back to development secret in development/test environments\n */\nfunction getJwtSecret(): string {\n\tconst walletKey = process.env.AGENT_WALLET_KEY\n\tif (walletKey) {\n\t\treturn resolveAgentSecret(walletKey)\n\t}\n\n\tconst nodeEnv = process.env.NODE_ENV || \"development\"\n\tif (nodeEnv === \"production\") {\n\t\tthrow new Error(\n\t\t\t\"AGENT_WALLET_KEY must be set in production for JWT token signing.\"\n\t\t)\n\t}\n\tlogger.warn(\n\t\t\"โ ๏ธ [SECURITY] Using fallback JWT secret for development. \" +\n\t\t\t\"Set AGENT_WALLET_KEY for production.\"\n\t)\n\treturn \"fallback-secret-for-dev-only\"\n}\n\n/**\n * Gets the API key for authentication, with lazy initialization\n * Requires XMTP_API_KEY environment variable in production\n * Only falls back to development key in development/test environments\n */\nfunction getApiKey(): string {\n\tconst apiKey = process.env.XMTP_API_KEY\n\tconst nodeEnv = process.env.NODE_ENV || \"development\"\n\n\t// In production, require a real API key\n\tif (nodeEnv === \"production\" && !apiKey) {\n\t\tthrow new Error(\n\t\t\t\"XMTP_API_KEY environment variable is required in production. \" +\n\t\t\t\t\"Generate a secure random API key for authentication.\"\n\t\t)\n\t}\n\n\t// In development/test, allow fallback but warn only when actually used\n\tif (!apiKey) {\n\t\tlogger.warn(\n\t\t\t\"โ ๏ธ [SECURITY] Using fallback API key for development. \" +\n\t\t\t\t\"Set XMTP_API_KEY environment variable for production.\"\n\t\t)\n\t\treturn \"fallback-api-key-for-dev-only\"\n\t}\n\n\treturn apiKey\n}\n\n/**\n * JWT token expiry time in seconds (5 minutes)\n */\nconst JWT_EXPIRY = 5 * 60 // 5 minutes in seconds\n\n/**\n * Generates a signed JWT token for XMTP tools authentication\n *\n * @param {Omit<XMTPToolsPayload, \"issued\" | \"expires\">} payload - Token payload without timestamp fields\n * @returns {string} Signed JWT token\n *\n * @description\n * Creates a JWT token with automatic timestamp fields:\n * - issued: Current timestamp\n * - expires: Current timestamp + JWT_EXPIRY\n *\n * @example\n * ```typescript\n * const token = generateXMTPToolsToken({\n * action: \"send\",\n * conversationId: \"0x123...\"\n * });\n * ```\n */\nexport function generateXMTPToolsToken(\n\tpayload: Omit<XMTPToolsPayload, \"issued\" | \"expires\">\n): string {\n\tconst startTime = performance.now()\n\tlogger.debug(\"๐ [JWT] Starting token generation...\")\n\n\tconst now = Math.floor(Date.now() / 1000)\n\tconst fullPayload: XMTPToolsPayload = {\n\t\t...payload,\n\t\tissued: now,\n\t\texpires: now + JWT_EXPIRY\n\t}\n\n\tconst token = jwt.sign(fullPayload, getJwtSecret(), {\n\t\texpiresIn: JWT_EXPIRY\n\t})\n\n\tconst endTime = performance.now()\n\tlogger.debug(\n\t\t`๐ [JWT] Token generation completed in ${(endTime - startTime).toFixed(2)}ms`\n\t)\n\n\treturn token\n}\n\n/**\n * Validates an XMTP tools token using either API key or JWT verification\n *\n * @param {string} token - Token to validate (either API key or JWT)\n * @returns {XMTPToolsPayload | null} Validated payload or null if invalid\n *\n * @description\n * Supports two authentication methods in order of precedence:\n * 1. API key authentication - Direct comparison with XMTP_API_KEY\n * 2. JWT token authentication - Signature verification and expiry check\n *\n * For API key authentication, returns a default payload with 1-hour expiry.\n * For JWT authentication, validates signature and checks expiry timestamp.\n *\n * @example\n * ```typescript\n * const payload = validateXMTPToolsToken(userToken);\n * if (payload) {\n * console.log(`Action: ${payload.action}`);\n * console.log(`Conversation: ${payload.conversationId}`);\n * }\n * ```\n */\nexport function validateXMTPToolsToken(token: string): XMTPToolsPayload | null {\n\tconst startTime = performance.now()\n\tlogger.debug(\"๐ [JWT] Starting token validation...\")\n\n\t// First try API key authentication\n\tif (token === getApiKey()) {\n\t\tlogger.debug(\"๐ [Auth] Using API key authentication\")\n\t\t// Return a valid payload for API key auth\n\t\tconst now = Math.floor(Date.now() / 1000)\n\t\tconst result = {\n\t\t\taction: \"send\" as const, // Default action\n\t\t\tconversationId: \"\", // Will be filled by endpoint\n\t\t\tissued: now,\n\t\t\texpires: now + 3600 // API keys are valid for 1 hour\n\t\t}\n\n\t\tconst endTime = performance.now()\n\t\tlogger.debug(\n\t\t\t`๐ [JWT] API key validation completed in ${(endTime - startTime).toFixed(2)}ms`\n\t\t)\n\t\treturn result\n\t}\n\n\t// Then try JWT token authentication\n\ttry {\n\t\tconst decoded = jwt.verify(token, getJwtSecret()) as XMTPToolsPayload\n\t\tlogger.debug(\"๐ [Auth] Using JWT token authentication\")\n\n\t\t// Additional expiry check\n\t\tconst now = Math.floor(Date.now() / 1000)\n\t\tif (decoded.expires < now) {\n\t\t\tconsole.log(\"๐ XMTP tools token has expired\")\n\t\t\tconst endTime = performance.now()\n\t\t\tlogger.debug(\n\t\t\t\t`๐ [JWT] Token validation failed (expired) in ${(endTime - startTime).toFixed(2)}ms`\n\t\t\t)\n\t\t\treturn null\n\t\t}\n\n\t\tconst endTime = performance.now()\n\t\tlogger.debug(\n\t\t\t`๐ [JWT] JWT validation completed in ${(endTime - startTime).toFixed(2)}ms`\n\t\t)\n\t\treturn decoded\n\t} catch (error) {\n\t\tlogger.error(\"๐ Invalid XMTP tools token and not matching API key:\", error)\n\t\tconst endTime = performance.now()\n\t\tlogger.debug(\n\t\t\t`๐ [JWT] Token validation failed in ${(endTime - startTime).toFixed(2)}ms`\n\t\t)\n\t\treturn null\n\t}\n}\n"],"mappings":"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","names":["createSigner","createUser","Client","toBytes","toBytes","Client","identifier","address","createSigner","createUser","logger","logger","createUser","createSigner","logger","logger","logger","Client","IdentifierKind","ReplyCodec"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hybrd/xmtp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -25,20 +25,21 @@
|
|
|
25
25
|
"@xmtp/content-type-transaction-reference": "2.0.2",
|
|
26
26
|
"@xmtp/content-type-wallet-send-calls": "2.0.0",
|
|
27
27
|
"@xmtp/node-sdk": "^4.1.0",
|
|
28
|
+
"dotenv": "^16.4.5",
|
|
28
29
|
"hono": "^4.7.4",
|
|
29
30
|
"jsonwebtoken": "^9.0.2",
|
|
30
31
|
"uint8arrays": "^5.1.0",
|
|
31
32
|
"viem": "^2.22.17",
|
|
32
|
-
"@hybrd/types": "
|
|
33
|
-
"@hybrd/utils": "
|
|
33
|
+
"@hybrd/types": "2.0.0",
|
|
34
|
+
"@hybrd/utils": "2.0.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/jsonwebtoken": "^9.0.7",
|
|
37
38
|
"@types/node": "22.8.6",
|
|
38
39
|
"tsup": "^8.5.0",
|
|
39
40
|
"vitest": "^3.2.4",
|
|
40
|
-
"@config/
|
|
41
|
-
"@config/
|
|
41
|
+
"@config/tsconfig": "0.0.0",
|
|
42
|
+
"@config/biome": "0.0.0"
|
|
42
43
|
},
|
|
43
44
|
"scripts": {
|
|
44
45
|
"build": "tsup",
|
|
@@ -49,6 +50,9 @@
|
|
|
49
50
|
"typecheck": "tsc --noEmit",
|
|
50
51
|
"lint": "biome lint --unsafe",
|
|
51
52
|
"lint:fix": "biome lint --write --unsafe",
|
|
52
|
-
"format": "biome format --write"
|
|
53
|
+
"format": "biome format --write",
|
|
54
|
+
"register": "tsx scripts/register-wallet.ts",
|
|
55
|
+
"revoke": "tsx scripts/revoke-installations.ts",
|
|
56
|
+
"revoke-all": "tsx scripts/revoke-all-installations.ts"
|
|
53
57
|
}
|
|
54
58
|
}
|
package/src/client.ts
CHANGED
|
@@ -1,26 +1,21 @@
|
|
|
1
|
+
import { getRandomValues } from "node:crypto"
|
|
2
|
+
import fs from "node:fs"
|
|
3
|
+
import path from "node:path"
|
|
1
4
|
import { logger } from "@hybrd/utils"
|
|
2
5
|
import { ReactionCodec } from "@xmtp/content-type-reaction"
|
|
3
6
|
import { ReplyCodec } from "@xmtp/content-type-reply"
|
|
4
7
|
import { TransactionReferenceCodec } from "@xmtp/content-type-transaction-reference"
|
|
5
8
|
import { WalletSendCallsCodec } from "@xmtp/content-type-wallet-send-calls"
|
|
6
9
|
import { Client, IdentifierKind, type Signer, XmtpEnv } from "@xmtp/node-sdk"
|
|
7
|
-
import { getRandomValues } from "node:crypto"
|
|
8
|
-
import fs from "node:fs"
|
|
9
|
-
import path from "node:path"
|
|
10
|
-
import { fileURLToPath } from "node:url"
|
|
11
10
|
import { fromString, toString as uint8arraysToString } from "uint8arrays"
|
|
12
|
-
import {
|
|
11
|
+
import { http, createWalletClient, toBytes } from "viem"
|
|
13
12
|
import { privateKeyToAccount } from "viem/accounts"
|
|
14
13
|
import { sepolia } from "viem/chains"
|
|
15
14
|
import { revokeOldInstallations } from "../scripts/revoke-installations"
|
|
15
|
+
import { resolveAgentSecret } from "./lib/secret.js"
|
|
16
16
|
import { XmtpClient } from "./types"
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
// Module Setup
|
|
20
|
-
// ===================================================================
|
|
21
|
-
// ES module equivalent of __dirname
|
|
22
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
23
|
-
const __dirname = path.dirname(__filename)
|
|
18
|
+
export { deriveAgentSecret, resolveAgentSecret } from "./lib/secret.js"
|
|
24
19
|
|
|
25
20
|
// ===================================================================
|
|
26
21
|
// Type Definitions
|
|
@@ -86,11 +81,8 @@ async function clearXMTPDatabase(address: string, env: string) {
|
|
|
86
81
|
: path.resolve(process.cwd(), customStoragePath)
|
|
87
82
|
}
|
|
88
83
|
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
process.env.PROJECT_ROOT || path.resolve(__dirname, "../../..")
|
|
92
|
-
|
|
93
|
-
return path.join(projectRoot, ".data/xmtp") // Local development
|
|
84
|
+
// Default to .hybrid/.xmtp in current working directory
|
|
85
|
+
return path.join(process.cwd(), ".hybrid", ".xmtp")
|
|
94
86
|
}
|
|
95
87
|
|
|
96
88
|
// Clear local database files
|
|
@@ -103,6 +95,8 @@ async function clearXMTPDatabase(address: string, env: string) {
|
|
|
103
95
|
// Legacy fallback paths for backward compatibility
|
|
104
96
|
path.join(process.cwd(), ".data", "xmtp"),
|
|
105
97
|
path.join(process.cwd(), "..", ".data", "xmtp"),
|
|
98
|
+
path.join(process.cwd(), "..", "..", ".data", "xmtp"),
|
|
99
|
+
// Monorepo root fallback
|
|
106
100
|
path.join(process.cwd(), "..", "..", ".data", "xmtp")
|
|
107
101
|
]
|
|
108
102
|
|
|
@@ -150,11 +144,12 @@ export async function createXMTPClient(
|
|
|
150
144
|
|
|
151
145
|
if (!signer) {
|
|
152
146
|
throw new Error(
|
|
153
|
-
"No signer provided and
|
|
147
|
+
"No signer provided and AGENT_WALLET_KEY environment variable is not set"
|
|
154
148
|
)
|
|
155
149
|
}
|
|
156
150
|
|
|
157
|
-
const {
|
|
151
|
+
const { XMTP_ENV } = process.env
|
|
152
|
+
const agentSecret = resolveAgentSecret(privateKey)
|
|
158
153
|
|
|
159
154
|
// Get the wallet address to use the correct database
|
|
160
155
|
const identifier = await signer.getIdentifier()
|
|
@@ -175,13 +170,7 @@ export async function createXMTPClient(
|
|
|
175
170
|
)
|
|
176
171
|
}
|
|
177
172
|
|
|
178
|
-
|
|
179
|
-
throw new Error(
|
|
180
|
-
"XMTP_DB_ENCRYPTION_KEY must be set for persistent mode"
|
|
181
|
-
)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const dbEncryptionKey = getEncryptionKeyFromHex(XMTP_DB_ENCRYPTION_KEY)
|
|
173
|
+
const dbEncryptionKey = getEncryptionKeyFromHex(agentSecret)
|
|
185
174
|
const dbPath = await getDbPath(
|
|
186
175
|
`${XMTP_ENV || "dev"}-${address}`,
|
|
187
176
|
storagePath
|
|
@@ -220,7 +209,8 @@ export async function createXMTPClient(
|
|
|
220
209
|
|
|
221
210
|
if (
|
|
222
211
|
error instanceof Error &&
|
|
223
|
-
error.message.includes("
|
|
212
|
+
(error.message.includes("installations") ||
|
|
213
|
+
error.message.match(/\d+\/\d+\s+installations/))
|
|
224
214
|
) {
|
|
225
215
|
console.log(
|
|
226
216
|
`๐ฅ Installation limit reached (attempt ${attempt}/${maxRetries})`
|
|
@@ -280,9 +270,7 @@ export async function createXMTPClient(
|
|
|
280
270
|
// Try to refresh identity by creating a persistent client first
|
|
281
271
|
try {
|
|
282
272
|
console.log("๐ Creating persistent client to refresh identity...")
|
|
283
|
-
const tempEncryptionKey =
|
|
284
|
-
? getEncryptionKeyFromHex(XMTP_DB_ENCRYPTION_KEY)
|
|
285
|
-
: getEncryptionKeyFromHex(generateEncryptionKeyHex())
|
|
273
|
+
const tempEncryptionKey = getEncryptionKeyFromHex(agentSecret)
|
|
286
274
|
const tempClient = await Client.create(signer, {
|
|
287
275
|
dbEncryptionKey: tempEncryptionKey,
|
|
288
276
|
env: XMTP_ENV as XmtpEnv,
|
|
@@ -372,12 +360,8 @@ export const getDbPath = async (description = "xmtp", storagePath?: string) => {
|
|
|
372
360
|
? storagePath
|
|
373
361
|
: path.resolve(process.cwd(), storagePath)
|
|
374
362
|
} else {
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
process.env.PROJECT_ROOT || path.resolve(__dirname, "../../..")
|
|
378
|
-
|
|
379
|
-
// Default storage path for local development
|
|
380
|
-
volumePath = path.join(projectRoot, ".data/xmtp")
|
|
363
|
+
// Default to .hybrid/.xmtp in current working directory
|
|
364
|
+
volumePath = path.join(process.cwd(), ".hybrid", ".xmtp")
|
|
381
365
|
}
|
|
382
366
|
|
|
383
367
|
const dbPath = `${volumePath}/${description}.db3`
|
|
@@ -822,7 +806,3 @@ export async function createXMTPConnectionManager(
|
|
|
822
806
|
await manager.connect()
|
|
823
807
|
return manager
|
|
824
808
|
}
|
|
825
|
-
|
|
826
|
-
// ===================================================================
|
|
827
|
-
// User Address Resolution with Auto-Refresh
|
|
828
|
-
// ===================================================================
|
package/src/index.ts
CHANGED
|
@@ -20,11 +20,14 @@ export {
|
|
|
20
20
|
// XMTP Client and Connection Management
|
|
21
21
|
// ===================================================================
|
|
22
22
|
export {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
createXMTPClient,
|
|
24
|
+
createSigner as createXMTPSigner,
|
|
25
|
+
getDbPath,
|
|
26
|
+
logAgentDetails,
|
|
27
|
+
validateEnvironment,
|
|
28
|
+
XMTPConnectionManager,
|
|
29
|
+
deriveAgentSecret,
|
|
30
|
+
resolveAgentSecret
|
|
28
31
|
} from "./client"
|
|
29
32
|
export type { XMTPConnectionConfig } from "./client"
|
|
30
33
|
|
package/src/lib/jwt.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { logger } from "@hybrd/utils"
|
|
1
2
|
import { Context } from "hono"
|
|
2
3
|
import jwt from "jsonwebtoken"
|
|
3
|
-
import {
|
|
4
|
+
import { resolveAgentSecret } from "./secret.js"
|
|
4
5
|
|
|
5
6
|
export interface XMTPToolsPayload {
|
|
6
7
|
action: "send" | "reply" | "react" | "transaction" | "blockchain-event"
|
|
@@ -66,31 +67,26 @@ export function getValidatedPayload(c: Context): XMTPToolsPayload | null {
|
|
|
66
67
|
|
|
67
68
|
/**
|
|
68
69
|
* Gets the JWT secret for token signing, with lazy initialization
|
|
69
|
-
*
|
|
70
|
+
* Derives secret from AGENT_WALLET_KEY using BIP-32
|
|
70
71
|
* Only falls back to development secret in development/test environments
|
|
71
72
|
*/
|
|
72
73
|
function getJwtSecret(): string {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// In production, require a real JWT secret
|
|
77
|
-
if (nodeEnv === "production" && !secret) {
|
|
78
|
-
throw new Error(
|
|
79
|
-
"XMTP_DB_ENCRYPTION_KEY environment variable is required in production. " +
|
|
80
|
-
"Generate a secure random secret for JWT token signing."
|
|
81
|
-
)
|
|
74
|
+
const walletKey = process.env.AGENT_WALLET_KEY
|
|
75
|
+
if (walletKey) {
|
|
76
|
+
return resolveAgentSecret(walletKey)
|
|
82
77
|
}
|
|
83
78
|
|
|
84
|
-
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
"
|
|
88
|
-
"Set XMTP_DB_ENCRYPTION_KEY environment variable for production."
|
|
79
|
+
const nodeEnv = process.env.NODE_ENV || "development"
|
|
80
|
+
if (nodeEnv === "production") {
|
|
81
|
+
throw new Error(
|
|
82
|
+
"AGENT_WALLET_KEY must be set in production for JWT token signing."
|
|
89
83
|
)
|
|
90
|
-
return "fallback-secret-for-dev-only"
|
|
91
84
|
}
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
logger.warn(
|
|
86
|
+
"โ ๏ธ [SECURITY] Using fallback JWT secret for development. " +
|
|
87
|
+
"Set AGENT_WALLET_KEY for production."
|
|
88
|
+
)
|
|
89
|
+
return "fallback-secret-for-dev-only"
|
|
94
90
|
}
|
|
95
91
|
|
|
96
92
|
/**
|
|
@@ -239,10 +235,7 @@ export function validateXMTPToolsToken(token: string): XMTPToolsPayload | null {
|
|
|
239
235
|
)
|
|
240
236
|
return decoded
|
|
241
237
|
} catch (error) {
|
|
242
|
-
logger.error(
|
|
243
|
-
"๐ Invalid XMTP tools token and not matching API key:",
|
|
244
|
-
error
|
|
245
|
-
)
|
|
238
|
+
logger.error("๐ Invalid XMTP tools token and not matching API key:", error)
|
|
246
239
|
const endTime = performance.now()
|
|
247
240
|
logger.debug(
|
|
248
241
|
`๐ [JWT] Token validation failed in ${(endTime - startTime).toFixed(2)}ms`
|