import { z } from 'zod';
import type { DroydClient } from '../client.js';
// =============================================================================
// Search Projects
// =============================================================================
export const searchProjectsSchema = z.object({
search_type: z
.enum(['name', 'symbol', 'address', 'semantic', 'project_id'])
.describe('Type of search to perform'),
queries: z
.array(z.string())
.min(1)
.max(15)
.describe('Search queries (1-15 items)'),
limit: z
.number()
.min(1)
.max(25)
.optional()
.describe('Results per query (1-25)'),
include_attributes: z
.array(z.enum(['developments', 'recent_content', 'technical_analysis', 'market_data', 'mindshare', 'detailed_description', 'metadata']))
.optional()
.describe('Additional data to include'),
developments_limit: z
.number()
.min(1)
.max(10)
.optional()
.describe('Max developments per project (1-10)'),
recent_content_limit: z
.number()
.min(1)
.max(25)
.optional()
.describe('Max content items per project (1-25)'),
recent_content_days_back: z
.number()
.min(1)
.max(30)
.optional()
.describe('Days back for content (1-30)'),
});
export const searchProjectsTool = {
name: 'droyd_search_projects',
description: `Search for crypto projects by name, symbol, address, or semantic query.
**Search Types:**
- **project_id** - Direct ID lookup (fastest)
- **name** - Search by project name (e.g., "Jupiter", "Raydium")
- **symbol** - Search by ticker symbol (e.g., "JUP", "RAY", "SOL")
- **address** - Search by contract address (exact match)
- **semantic** - AI-powered concept search (e.g., "AI agents in DeFi")
**Attributes:**
developments, recent_content, technical_analysis, market_data, mindshare, detailed_description, metadata
**Examples:**
- By name: { "search_type": "name", "queries": ["Jupiter", "Raydium"] }
- By symbol: { "search_type": "symbol", "queries": ["SOL", "ETH", "BTC"] }
- Semantic: { "search_type": "semantic", "queries": ["liquid staking protocols on Solana"] }
- With data: { "search_type": "name", "queries": ["Bitcoin"], "include_attributes": ["market_data", "technical_analysis"] }`,
inputSchema: {
type: 'object' as const,
properties: {
search_type: {
type: 'string',
enum: ['name', 'symbol', 'address', 'semantic', 'project_id'],
description: 'Type of search',
},
queries: {
type: 'array',
items: { type: 'string' },
description: 'Search queries (1-15)',
minItems: 1,
maxItems: 15,
},
limit: {
type: 'number',
description: 'Results per query (1-25)',
minimum: 1,
maximum: 25,
},
include_attributes: {
type: 'array',
items: {
type: 'string',
enum: ['developments', 'recent_content', 'technical_analysis', 'market_data', 'mindshare', 'detailed_description', 'metadata'],
},
description: 'Additional data to include',
},
developments_limit: {
type: 'number',
description: 'Max developments per project (1-10)',
minimum: 1,
maximum: 10,
},
recent_content_limit: {
type: 'number',
description: 'Max content items per project (1-25)',
minimum: 1,
maximum: 25,
},
recent_content_days_back: {
type: 'number',
description: 'Days back for content (1-30)',
minimum: 1,
maximum: 30,
},
},
required: ['search_type', 'queries'],
},
};
export async function handleSearchProjects(
client: DroydClient,
args: Record<string, unknown>
) {
const parsed = searchProjectsSchema.parse(args);
return client.searchProjects(parsed);
}
// =============================================================================
// Filter Projects
// =============================================================================
export const filterProjectsSchema = z.object({
filter_mode: z
.enum(['natural_language', 'direct'])
.describe('Filter mode'),
instructions: z
.string()
.min(10)
.optional()
.describe('Natural language instructions (required for natural_language mode)'),
sort_by: z
.string()
.optional()
.describe('Sort field'),
sort_direction: z
.enum(['asc', 'desc'])
.optional()
.describe('Sort direction'),
timeframe: z
.enum(['4h', '24h'])
.optional()
.describe('Timeframe for metrics'),
tradable_chains: z
.array(z.string())
.optional()
.describe('Filter by chains'),
min_market_cap: z
.number()
.optional()
.describe('Min market cap in MILLIONS'),
max_market_cap: z
.number()
.optional()
.describe('Max market cap in MILLIONS'),
min_price_change: z.number().optional().describe('Min price change %'),
max_price_change: z.number().optional().describe('Max price change %'),
min_liquidity: z.number().optional().describe('Min liquidity in USD'),
min_volume: z.number().optional().describe('Min volume in USD'),
min_trader_count: z.number().optional().describe('Min unique traders'),
min_trader_change: z.number().optional().describe('Min trader change %'),
min_technical_score: z.number().optional().describe('Min quant score (-100 to 100)'),
max_technical_score: z.number().optional().describe('Max quant score'),
min_rsi: z.number().optional().describe('Min RSI (0-100)'),
max_rsi: z.number().optional().describe('Max RSI'),
limit: z.number().min(1).max(50).optional().describe('Results (1-50)'),
page: z.number().min(0).optional().describe('Page number (0-based)'),
include_attributes: z
.array(z.enum(['developments', 'recent_content', 'technical_analysis', 'market_data', 'mindshare', 'detailed_description', 'metadata']))
.optional()
.describe('Additional data to include'),
});
export const filterProjectsTool = {
name: 'droyd_filter_projects',
description: `Filter and screen crypto projects using market criteria.
**Filter Modes:**
- **natural_language** - Describe what you want (e.g., "trending micro-cap Solana tokens")
- **direct** - Use specific filter parameters
**Sort Options:**
market_cap, price_change, traders, traders_change, volume, volume_change, buy_volume_ratio, quant_score, quant_score_change, mentions_24h, mentions_7d
**Chains:**
solana, ethereum, base, arbitrum
**Note:** Market cap values are in MILLIONS (e.g., max_market_cap: 10 = $10M)
**Examples:**
- Natural language: { "filter_mode": "natural_language", "instructions": "Find trending micro-cap Solana tokens with high trader growth" }
- Direct filters: { "filter_mode": "direct", "sort_by": "traders_change", "tradable_chains": ["solana"], "max_market_cap": 10, "min_liquidity": 50000 }
- Oversold tokens: { "filter_mode": "direct", "sort_by": "quant_score", "sort_direction": "asc", "max_rsi": 30 }`,
inputSchema: {
type: 'object' as const,
properties: {
filter_mode: {
type: 'string',
enum: ['natural_language', 'direct'],
description: 'Filter mode',
},
instructions: {
type: 'string',
description: 'Natural language instructions (min 10 chars)',
minLength: 10,
},
sort_by: {
type: 'string',
description: 'Sort field',
},
sort_direction: {
type: 'string',
enum: ['asc', 'desc'],
description: 'Sort direction',
},
timeframe: {
type: 'string',
enum: ['4h', '24h'],
description: 'Timeframe for metrics',
},
tradable_chains: {
type: 'array',
items: { type: 'string' },
description: 'Filter by chains (solana, ethereum, base, arbitrum)',
},
min_market_cap: {
type: 'number',
description: 'Min market cap in MILLIONS',
},
max_market_cap: {
type: 'number',
description: 'Max market cap in MILLIONS',
},
min_price_change: { type: 'number', description: 'Min price change %' },
max_price_change: { type: 'number', description: 'Max price change %' },
min_liquidity: { type: 'number', description: 'Min liquidity in USD' },
min_volume: { type: 'number', description: 'Min volume in USD' },
min_trader_count: { type: 'number', description: 'Min unique traders' },
min_trader_change: { type: 'number', description: 'Min trader change %' },
min_technical_score: { type: 'number', description: 'Min quant score (-100 to 100)' },
max_technical_score: { type: 'number', description: 'Max quant score' },
min_rsi: { type: 'number', description: 'Min RSI (0-100)' },
max_rsi: { type: 'number', description: 'Max RSI' },
limit: {
type: 'number',
description: 'Results (1-50)',
minimum: 1,
maximum: 50,
},
page: {
type: 'number',
description: 'Page number (0-based)',
minimum: 0,
},
include_attributes: {
type: 'array',
items: {
type: 'string',
enum: ['developments', 'recent_content', 'technical_analysis', 'market_data', 'mindshare', 'detailed_description', 'metadata'],
},
description: 'Additional data to include',
},
},
required: ['filter_mode'],
},
};
export async function handleFilterProjects(
client: DroydClient,
args: Record<string, unknown>
) {
const parsed = filterProjectsSchema.parse(args);
return client.filterProjects(parsed);
}
// =============================================================================
// Watchlist
// =============================================================================
export const watchlistSchema = z.object({
scope: z
.enum(['agent', 'swarm', 'combined'])
.optional()
.default('combined')
.describe('Watchlist scope'),
include_attributes: z
.array(z.enum(['developments', 'recent_content', 'technical_analysis', 'market_data', 'mindshare', 'detailed_description', 'metadata']))
.optional()
.describe('Additional data to include'),
limit: z
.number()
.min(1)
.max(50)
.optional()
.describe('Results (1-50)'),
});
export const watchlistTool = {
name: 'droyd_get_watchlist',
description: `Get watchlist projects for the authenticated user.
**Scopes:**
- **agent** - Personal agent watchlist only
- **swarm** - Community swarm watchlists
- **combined** - Both personal and swarm (default)
Returns projects with agent evaluations including investment scores and thesis points.
**Examples:**
- Get combined: { "scope": "combined" }
- Agent only: { "scope": "agent", "include_attributes": ["market_data", "technical_analysis"] }
- With limit: { "scope": "swarm", "limit": 10 }`,
inputSchema: {
type: 'object' as const,
properties: {
scope: {
type: 'string',
enum: ['agent', 'swarm', 'combined'],
description: 'Watchlist scope',
default: 'combined',
},
include_attributes: {
type: 'array',
items: {
type: 'string',
enum: ['developments', 'recent_content', 'technical_analysis', 'market_data', 'mindshare', 'detailed_description', 'metadata'],
},
description: 'Additional data to include',
},
limit: {
type: 'number',
description: 'Results (1-50)',
minimum: 1,
maximum: 50,
},
},
required: [],
},
};
export async function handleGetWatchlist(
client: DroydClient,
args: Record<string, unknown>
) {
const parsed = watchlistSchema.parse(args);
return client.getWatchlist(parsed);
}