Add new XMTP channel extension for decentralized messaging: - Direct messages (DMs) and group conversations - Text messages, reactions, and attachments - ENS address resolution for human-readable names - Quantum-resistant encryption (MLS protocol) - Multi-account support with per-account configuration - Database isolation and network selection (dev/production) - Comprehensive onboarding with wallet generation Includes wallet generation scripts, example configurations, troubleshooting guide, and test coverage for schemas, ENS resolution, multi-account handling, and onboarding flows. Note: Tests use Node's test runner format (node:test) and should be converted to Vitest format in follow-up work.
180 lines
5.4 KiB
TypeScript
180 lines
5.4 KiB
TypeScript
/**
|
|
* XMTP Channel Zod Schemas
|
|
*
|
|
* Zod validation schemas for XMTP channel configuration.
|
|
* These schemas provide runtime validation and type inference.
|
|
*/
|
|
|
|
import { z } from "zod";
|
|
|
|
// ============================================================================
|
|
// Base Schemas
|
|
// ============================================================================
|
|
|
|
/**
|
|
* XMTP network environment schema.
|
|
*/
|
|
export const XmtpEnvSchema = z.enum(["dev", "production"]);
|
|
|
|
/**
|
|
* DM policy schema for XMTP channel.
|
|
*/
|
|
export const XmtpDmPolicySchema = z.enum(["pairing", "allowlist", "open"]);
|
|
|
|
/**
|
|
* Markdown table mode schema.
|
|
*/
|
|
export const XmtpMarkdownTableModeSchema = z.enum(["off", "bullets", "code"]);
|
|
|
|
// ============================================================================
|
|
// Configuration Schemas
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Markdown configuration schema.
|
|
*/
|
|
export const XmtpMarkdownConfigSchema = z.object({
|
|
tables: XmtpMarkdownTableModeSchema.optional(),
|
|
});
|
|
|
|
/**
|
|
* Action configuration schema.
|
|
*/
|
|
export const XmtpActionConfigSchema = z.object({
|
|
reactions: z.boolean().optional(),
|
|
sendMessage: z.boolean().optional(),
|
|
});
|
|
|
|
/**
|
|
* Retry configuration schema.
|
|
*/
|
|
export const XmtpRetryConfigSchema = z.object({
|
|
attempts: z.number().int().min(0).max(10).optional(),
|
|
minDelayMs: z.number().int().min(0).max(60000).optional(),
|
|
maxDelayMs: z.number().int().min(0).max(300000).optional(),
|
|
jitter: z.number().min(0).max(1).optional(),
|
|
});
|
|
|
|
/**
|
|
* Ethereum wallet key schema.
|
|
* Validates 0x-prefixed 64-character hex string.
|
|
*/
|
|
export const WalletKeySchema = z
|
|
.string()
|
|
.regex(/^0x[a-fA-F0-9]{64}$/, "Must be 0x-prefixed 64-character hex string")
|
|
.optional();
|
|
|
|
/**
|
|
* Ethereum address schema.
|
|
* Validates 0x-prefixed 40-character hex string.
|
|
*/
|
|
export const EthereumAddressSchema = z
|
|
.string()
|
|
.regex(/^0x[a-fA-F0-9]{40}$/i, "Must be 0x-prefixed 40-character hex address")
|
|
.transform((addr) => addr.toLowerCase());
|
|
|
|
/**
|
|
* Per-account XMTP configuration schema.
|
|
*/
|
|
export const XmtpAccountConfigSchema = z.object({
|
|
name: z.string().optional(),
|
|
enabled: z.boolean().optional(),
|
|
walletKey: WalletKeySchema,
|
|
env: XmtpEnvSchema.default("dev"),
|
|
dbPath: z.string().default(".xmtp/db"),
|
|
encryptionKey: z.string().optional(),
|
|
dmPolicy: XmtpDmPolicySchema.default("pairing"),
|
|
allowFrom: z.array(z.string()).default([]),
|
|
markdown: XmtpMarkdownConfigSchema.optional(),
|
|
textChunkLimit: z.number().int().min(100).max(10000).default(4000),
|
|
chunkMode: z.enum(["length", "newline"]).default("length"),
|
|
actions: XmtpActionConfigSchema.optional(),
|
|
retry: XmtpRetryConfigSchema.optional(),
|
|
reactionNotifications: z.enum(["off", "own", "all"]).default("off"),
|
|
reactionLevel: z.enum(["off", "ack", "minimal", "extensive"]).default("ack"),
|
|
});
|
|
|
|
/**
|
|
* Top-level XMTP channel configuration schema.
|
|
* Supports both single-account and multi-account setups.
|
|
*/
|
|
export const XmtpConfigSchema = XmtpAccountConfigSchema.extend({
|
|
accounts: z.record(z.string(), XmtpAccountConfigSchema).optional(),
|
|
});
|
|
|
|
/**
|
|
* Simplified config schema for buildChannelConfigSchema.
|
|
* This is the schema passed to clawdbot's config builder.
|
|
*/
|
|
export const XmtpChannelConfigSchema = z.object({
|
|
enabled: z.boolean().default(false),
|
|
walletKey: z.string().optional(),
|
|
env: XmtpEnvSchema.default("dev"),
|
|
dbPath: z.string().default(".xmtp/db"),
|
|
encryptionKey: z.string().optional(),
|
|
dmPolicy: XmtpDmPolicySchema.default("pairing"),
|
|
allowFrom: z.array(z.string()).default([]),
|
|
});
|
|
|
|
// ============================================================================
|
|
// Type Inference
|
|
// ============================================================================
|
|
|
|
/** Inferred type from XmtpEnvSchema */
|
|
export type XmtpEnvInferred = z.infer<typeof XmtpEnvSchema>;
|
|
|
|
/** Inferred type from XmtpDmPolicySchema */
|
|
export type XmtpDmPolicyInferred = z.infer<typeof XmtpDmPolicySchema>;
|
|
|
|
/** Inferred type from XmtpAccountConfigSchema */
|
|
export type XmtpAccountConfigInferred = z.infer<typeof XmtpAccountConfigSchema>;
|
|
|
|
/** Inferred type from XmtpConfigSchema */
|
|
export type XmtpConfigInferred = z.infer<typeof XmtpConfigSchema>;
|
|
|
|
// ============================================================================
|
|
// Validation Helpers
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Validate an Ethereum address.
|
|
* Returns lowercase normalized address or throws.
|
|
*/
|
|
export function validateEthereumAddress(address: string): string {
|
|
const result = EthereumAddressSchema.safeParse(address);
|
|
if (!result.success) {
|
|
throw new Error(`Invalid Ethereum address: ${result.error.message}`);
|
|
}
|
|
return result.data;
|
|
}
|
|
|
|
/**
|
|
* Validate a wallet key.
|
|
* Returns the key or throws.
|
|
*/
|
|
export function validateWalletKey(key: string): string {
|
|
const result = WalletKeySchema.safeParse(key);
|
|
if (!result.success) {
|
|
throw new Error(`Invalid wallet key: ${result.error.message}`);
|
|
}
|
|
return key;
|
|
}
|
|
|
|
/**
|
|
* Validate XMTP account configuration.
|
|
* Returns validated config with defaults applied.
|
|
*/
|
|
export function validateAccountConfig(
|
|
config: unknown
|
|
): XmtpAccountConfigInferred {
|
|
return XmtpAccountConfigSchema.parse(config);
|
|
}
|
|
|
|
/**
|
|
* Safely validate XMTP account configuration.
|
|
* Returns result object with success flag.
|
|
*/
|
|
export function safeValidateAccountConfig(config: unknown) {
|
|
return XmtpAccountConfigSchema.safeParse(config);
|
|
}
|