UNPKG

mcp-price-strategy-server

Version:
1,229 lines (1,149 loc) 61.8 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import dayjs from 'dayjs'; import ApiClient from './utils/api-client.js'; import Validator from './utils/validator.js'; import config from './config/index.js'; import fs from 'fs'; import path from 'path'; // 日志工具类 class Logger { static log(level, message, data = null) { const timestamp = new Date().toISOString(); const logEntry = { timestamp, level, message, data, }; // 输出到stderr,避免影响MCP协议 console.error(`[${timestamp}] [${level}] ${message}`, data ? JSON.stringify(data, null, 2) : ''); // 同时写入文件 const logDir = path.join(process.cwd(), 'logs'); if (!fs.existsSync(logDir)) { fs.mkdirSync(logDir, { recursive: true }); } const logFile = path.join(logDir, `mcp-server-${new Date().toISOString().split('T')[0]}.log`); const logLine = `[${timestamp}] [${level}] ${message}${data ? ' ' + JSON.stringify(data, null, 2) : ''}\n`; fs.appendFileSync(logFile, logLine); } static info(message, data = null) { this.log('INFO', message, data); } static debug(message, data = null) { this.log('DEBUG', message, data); } static warn(message, data = null) { this.log('WARN', message, data); } static error(message, data = null) { this.log('ERROR', message, data); } } class StrategyMCPServer extends Server { constructor() { super({ name: config.server.name, version: config.server.version, }); this.apiClient = new ApiClient(); this.token = config.api.token; // this.userInfo = null; this.tools = new Map(); Logger.info('MCP服务器初始化开始'); this.registerTools(); this.registerHandlers(); Logger.info('MCP服务器初始化完成'); console.log('config ==============', config.api); } registerTools() { Logger.info('开始注册工具'); // 注册登录工具 // this.tools.set('login', { // description: '用户账户登录', // inputSchema: { // type: 'object', // properties: { // email: { // type: 'string', // description: '用户邮箱', // }, // password: { // type: 'string', // description: '用户密码', // }, // }, // required: ['email', 'password'], // }, // handler: async args => { // Logger.info('收到登录请求', { args }); // try { // Logger.debug('验证登录参数', args); // Validator.validateLoginParams(args); // Logger.info('调用API登录', { email: args.email }); // const response = await this.apiClient.login(args.email, args.password); // Logger.debug('API登录响应', response); // if (response.accessToken) { // this.token = response.accessToken; // this.userInfo = response.user; // this.apiClient.setToken(response.accessToken); // Logger.info('登录成功', { user: response.user }); // return { // content: [ // { // type: 'text', // text: config.messages.login.success, // }, // { // type: 'text', // text: `用户: ${JSON.stringify(response.user)}`, // }, // ], // }; // } else { // Logger.warn('登录失败', { message: response.message }); // return { // content: [ // { // type: 'text', // text: response.message || config.messages.login.failed, // }, // ], // }; // } // } catch (error) { // Logger.error('登录异常', { error: error.message, stack: error.stack }); // return { // content: [ // { // type: 'text', // text: error.message || config.messages.login.failed, // }, // ], // }; // } // }, // }); // 注册获取项目列表工具 this.tools.set('getProjects', { description: `获取用户的项目列表,只需要返回项目名称, 然后提示用户选择一个项目,再获取Token列表; 如果没有项目列表,则提示用户需要再MM管理后台(https://onchain.wired.fund)先创建一个项目;`, inputSchema: { type: 'object', properties: {}, }, handler: async args => { Logger.info('收到获取项目列表请求', { args }); try { if (!this.token) { Logger.warn('未登录状态尝试获取项目列表'); return { content: [ { type: 'text', text: config.messages.login.required, }, ], }; } Logger.info('调用API获取项目列表'); const response = await this.apiClient.getProjects(); Logger.debug('API项目列表响应', response); const projects = (response || []).filter(item => item.status === 'active'); return { content: [ { type: 'text', text: `获取项目列表成功,共 ${projects.length} 个项目`, }, { type: 'text', text: `项目列表: ${JSON.stringify(projects, null, 2)}`, }, ], }; } catch (error) { Logger.error('获取项目列表异常', { error: error.message, stack: error.stack }); return { content: [ { type: 'text', text: error.message || config.messages.projects.failed, }, ], }; } }, }); // 注册获取Token列表工具 this.tools.set('getTokens', { description: ` 获取指定项目里的Token列表,只需要返回地址、名称、和符号 (注意:不要返回其他信息), 然后提示用户选择一个Token,再提示获取钱包列表, 然后选择一个钱包,再提示创建策略; 如果没有项目ID, 则提示用户先获取项目列表, 需要把项目列表返回给用户,只需要返回项目名称, 然后提示用户选择一个项目,再获取Token列表; `, inputSchema: { type: 'object', properties: { projectId: { type: 'string', description: '项目ID', }, page: { type: 'number', description: '页码', default: 1, }, limit: { type: 'number', description: '每页数量', default: 10000, }, }, required: ['projectId'], }, handler: async args => { Logger.info('收到获取Token列表请求', { args }); try { if (!this.token) { Logger.warn('未登录状态尝试获取Token列表'); return { content: [ { type: 'text', text: config.messages.login.required, }, ], }; } Logger.debug('验证分页参数', args); Validator.validatePaginationParams(args); Logger.info('调用API获取Token列表', { projectId: args.projectId, page: args.page, limit: args.limit }); const response = await this.apiClient.getTokens(args.projectId, args.page, args.limit); Logger.debug('APIToken列表响应', response); const tokens = (response.items || []).map(ele => ({ ...ele, tradingType: ele.poolType === 'pump' ? 'inside' : 'outside' })); return { content: [ { type: 'text', text: `获取Token列表成功,项目ID: ${args.projectId},共 ${tokens.length} 个Token`, }, { type: 'text', text: `Token列表: ${JSON.stringify(tokens, null, 2)}`, }, ], }; } catch (error) { Logger.error('获取Token列表异常', { error: error.message, stack: error.stack }); return { content: [ { type: 'text', text: error.message || config.messages.tokens.failed, }, ], }; } }, }); // 注册获取钱包列表工具 this.tools.set('getWallets', { description: ` 用户必须先选要执行的择策略类型: 限价策略(PRICE_BASED)、定时策略(TIME_BASED)、拉砸策略(MARKET_MANIPULATION)、拆分策略(PORTFOLIO_EXCHANGE)、刷量策略(BUNDLE_SWAP); 交易方向: 1:限价策略(PRICE_BASED)、定时策略(TIME_BASED),则必须要先引导用户选择交易方向,buy或者sell, 先选择交易方向,再返回钱包列表; 2:拉砸策略(MARKET_MANIPULATION),则必须要先引导用户选择交易方向,拉升或者砸盘, (拉升为buy, 砸盘为sell), 先选择交易方向,再返回钱包列表; 3:其他策略不需要选择交易方向,也不需要提示给用户, 都设置为sell; 选择钱包限制: 1: 限价策略、定时策略、拉砸策略, 可以选择一个或者多个钱包来创建策略; 2: 拆分策略, 用户需要选择拆分地址和目标地址,都可以选择一个或者多个钱包,但是拆分地址和目标地址不能有交集; 3: 刷量策略, 用户需要选择买入地址和卖出地址,只能选择一个钱包地址, 买入地址和卖出地址不能相同; 获取指定Token的钱包列表,只需要返回钱包地址、SOL余额、当前Token的余额、别名(name)和对应的钱包组 (钱包组为 列表里的type和tag字段,拼接方式: type-tag), 然后引导创建对应的策略; 提示用户如果交易方向为买入, 则需要购买的钱包地址有SOL余额, 如果交易方向为卖出, 则需要卖出的钱包地址有当前Token余额和SOl余额; 如果没有Token ID, 则提示用户先获取Token列表`, inputSchema: { type: 'object', properties: { projectId: { type: 'string', description: '项目ID', }, tokenId: { type: 'string', description: 'Token ID', }, strategyType: { type: 'string', description: '策略类型', enum: ['PRICE_BASED', 'TIME_BASED', 'MARKET_MANIPULATION', 'PORTFOLIO_EXCHANGE', 'BUNDLE_SWAP'], }, side: { type: 'string', description: '交易方向', enum: ['buy', 'sell'], }, page: { type: 'number', description: '页码', default: 1, }, limit: { type: 'number', description: '每页数量', default: 10000, }, }, required: ['projectId', 'tokenId', 'side', 'strategyType'], }, handler: async args => { Logger.info('收到获取钱包列表请求', { args }); try { if (!this.token) { Logger.warn('未登录状态尝试获取钱包列表'); return { content: [ { type: 'text', text: config.messages.login.required, }, ], }; } if (!args.side) { return { content: [ { type: 'text', text: '请先选择交易方向', }, ], }; } Logger.debug('验证分页参数', args); Validator.validatePaginationParams(args); const response = await this.apiClient.getWallets(args.projectId, args.tokenId, args.page, args.limit); Logger.debug('API钱包列表响应', response); // const wallets = (response.items || []).filter(item => Number(item.balance) > 0 || Number(item.tokenBalance) > 0); const wallets = (response.items || []).filter(item => (args.side === 'buy' ? Number(item.balance) > 0 : Number(item.balance) > 0 && Number(item.tokenBalance) > 0)); return { content: [ { type: 'text', text: `获取钱包列表成功,Token ID: ${args.tokenId},共 ${wallets.length} 个钱包`, }, { type: 'text', text: `钱包列表: ${JSON.stringify(wallets, null, 2)}`, }, ], }; } catch (error) { Logger.error('获取钱包列表异常', { error: error.message, stack: error.stack }); return { content: [ { type: 'text', text: error.message || config.messages.wallets.failed, }, ], }; } }, }); // 如果交易方向为买入, 则需要购买的钱包地址有SOL余额, 并且数量大于0, 如果交易方向为卖出, 则需要卖出的钱包地址有Token余额和少量的SOl来做gas费用, 并且数量大于0; // 注册限价策略 this.tools.set('createPriceStrategy', { description: ` 创建限价策略订单, 交易方向,不需要用户输入, 从获取钱包列表的参数里获取; 交易类型,不需要用户输入, 也不需要告诉用户交易类型, 根据Token列表里的poolType字段来判断, 如果poolType为pump, 则交易类型为inside, 如果poolType为pool, 则交易类型为outside; 如果没有钱包ID,则提示用户先获取钱包列表; 如果没有Token ID,则提示用户先获取Token列表; 如果没有项目ID,则提示用户先获取项目列表; `, inputSchema: { type: 'object', properties: { tokenId: { type: 'string', description: 'Token ID', }, side: { type: 'string', description: '交易方向', enum: ['buy', 'sell'], default: 'buy', }, targetPrice: { type: 'number', description: '目标价格(单位: SOL)', }, walletIds: { type: 'array', items: { type: 'string', }, description: '钱包ID列表', }, amountType: { type: 'string', description: '数量类型, fixed: 固定数量, range: 余额比例(1-100%), random: 随机数量; 需要先让用户选择数量类型', enum: ['fixed', 'range', 'random'], default: 'fixed', }, amount: { type: 'number', description: '固定数量(单位: 买入为SOL, 卖出为Token数量, 注意: 是每个钱包地址的挂单数量, 如果数量类型为fixed, 则必须要输入;需要先让用户选择数量类型)', }, minRatio: { type: 'number', description: '范围比例最小值(单位: %, 如果数量类型为range, 则需要输入范围比例最小值和最大值;需要先让用户选择数量类型)', }, maxRatio: { type: 'number', description: '范围比例最大值(单位: %, 如果数量类型为range, 则需要输入范围比例最小值和最大值;需要先让用户选择数量类型)', }, minAmount: { type: 'number', description: '随机数量最小值(单位: 买入为SOL, 卖出为Token数量, 如果数量类型为random, 则需要输入随机数量最小值和最大值;需要先让用户选择数量类型)', }, maxAmount: { type: 'number', description: '随机数量最大值(单位: 买入为SOL, 卖出为Token数量, 如果数量类型为random, 则需要输入随机数量最小值和最大值;需要先让用户选择数量类型)', }, tradingType: { type: 'string', description: '交易类型', enum: ['inside', 'outside'], default: 'outside', }, minInterval: { type: 'number', description: '最小交易间隔(秒)', default: 1, }, maxInterval: { type: 'number', description: '最大交易间隔(秒)', default: 2, }, tipAmount: { type: 'number', description: '小费金额(单位: SOL)', default: 0.0001, }, slippageBps: { type: 'number', description: '滑点(单位: %)', default: 5, }, }, required: ['tokenId', 'targetPrice', 'side', 'walletIds', 'tradingType'], }, handler: async args => { try { if (!this.token) { return { content: [ { type: 'text', text: config.messages.login.required, }, ], }; } // console.log('handler createStrategy args ==============', args); // 验证策略参数 const validationArgs = { ...args, }; Validator.validatePriceStrategyParams(validationArgs); const tradingParams = { side: args.side || 'buy', tradingType: args.tradingType || 'inside', targetPrice: args.targetPrice, priceThresholdPercent: args.priceThresholdPercent || 0, walletIds: args.walletIds, minInterval: (args.minInterval || config.strategy.defaultInterval.min) * 1000, maxInterval: (args.maxInterval || config.strategy.defaultInterval.max) * 1000, }; if (args.amountType === 'fixed') { tradingParams.amount = args.amount; } else if (args.amountType === 'range') { tradingParams.minRatio = args.minRatio; tradingParams.maxRatio = args.maxRatio; } else if (args.amountType === 'random') { tradingParams.minAmount = args.minAmount; tradingParams.maxAmount = args.maxAmount; } if (args.tipAmount) { tradingParams.tipAmount = args.tipAmount; } if (tradingParams.tradingType === 'outside' && args.slippageBps) { tradingParams.slippageBps = args.slippageBps; } const strategyParams = { name: 'PRICE_BASED', type: 'PRICE_BASED', tokenId: args.tokenId, config: tradingParams, }; const response = await this.apiClient.createStrategy(strategyParams); if (response.success) { return { content: [ { type: 'text', text: config.messages.strategy.priceStrategySuccess, }, { type: 'text', text: `策略参数: ${JSON.stringify(strategyParams, null, 2)}`, }, ], }; } else { return { content: [ { type: 'text', text: response.message || config.messages.strategy.priceStrategyFailed, }, ], }; } } catch (error) { return { content: [ { type: 'text', text: error.message || config.messages.strategy.priceStrategyFailed, }, ], }; } }, }); // 注册定时策略 this.tools.set('createTimeStrategy', { description: ` 创建定时策略订单, 交易方向,不需要用户输入, 从获取钱包列表的参数里获取; 策略执行时间,必须要大于当前时间+3分钟, 格式为: 2025-01-01 12:00:00; 交易类型,不需要用户输入, 也不需要告诉用户交易类型, 根据Token列表里的poolType字段来判断, 如果poolType为pump, 则交易类型为inside, 如果poolType为pool, 则交易类型为outside; 如果没有钱包ID,则提示用户先获取钱包列表; 如果没有Token ID,则提示用户先获取Token列表; 如果没有项目ID,则提示用户先获取项目列表; `, inputSchema: { type: 'object', properties: { tokenId: { type: 'string', description: 'Token ID', }, side: { type: 'string', description: '交易方向', enum: ['buy', 'sell'], // default: 'buy', }, executeAt: { type: 'string', description: '策略执行时间(格式: 2025-01-01 12:00:00)', }, walletIds: { type: 'array', items: { type: 'string', }, description: '钱包ID列表', }, amountType: { type: 'string', description: '数量类型, fixed: 固定数量, range: 余额比例(1-100%), random: 随机数量; 需要先让用户选择数量类型', enum: ['fixed', 'range', 'random'], default: 'fixed', }, amount: { type: 'number', description: '固定数量(单位: 买入为SOL, 卖出为Token数量, 注意: 是每个钱包地址的挂单数量, 如果数量类型为fixed, 则必须要输入;需要先让用户选择数量类型)', }, minRatio: { type: 'number', description: '范围比例最小值(单位: %, 如果数量类型为range, 则需要输入范围比例最小值和最大值;需要先让用户选择数量类型)', }, maxRatio: { type: 'number', description: '范围比例最大值(单位: %, 如果数量类型为range, 则需要输入范围比例最小值和最大值;需要先让用户选择数量类型)', }, minAmount: { type: 'number', description: '随机数量最小值(单位: 买入为SOL, 卖出为Token数量, 如果数量类型为random, 则需要输入随机数量最小值和最大值;需要先让用户选择数量类型)', }, maxAmount: { type: 'number', description: '随机数量最大值(单位: 买入为SOL, 卖出为Token数量, 如果数量类型为random, 则需要输入随机数量最小值和最大值;需要先让用户选择数量类型)', }, tradingType: { type: 'string', description: '交易类型', enum: ['inside', 'outside'], // default: 'outside', }, minInterval: { type: 'number', description: '最小交易间隔(秒)', default: 1, }, maxInterval: { type: 'number', description: '最大交易间隔(秒)', default: 2, }, tipAmount: { type: 'number', description: '小费金额(单位: SOL)', default: 0.0001, }, slippageBps: { type: 'number', description: '滑点(单位: %)', default: 5, }, }, required: ['tokenId', 'executeAt', 'walletIds', 'tradingType', 'side'], }, handler: async args => { try { if (!this.token) { return { content: [ { type: 'text', text: config.messages.login.required, }, ], }; } // console.log('handler createStrategy args ==============', args); // 验证策略参数 const validationArgs = { ...args, }; Validator.validateTimeStrategyParams(validationArgs); const tradingParams = { side: args.side, tradingType: args.tradingType, executeAt: dayjs(values.executeAt).toISOString(), walletIds: args.walletIds, minInterval: (args.minInterval || config.strategy.defaultInterval.min) * 1000, maxInterval: (args.maxInterval || config.strategy.defaultInterval.max) * 1000, }; if (args.amountType === 'fixed') { tradingParams.amount = args.amount; } else if (args.amountType === 'range') { tradingParams.minRatio = args.minRatio; tradingParams.maxRatio = args.maxRatio; } else if (args.amountType === 'random') { tradingParams.minAmount = args.minAmount; tradingParams.maxAmount = args.maxAmount; } if (args.tipAmount) { tradingParams.tipAmount = args.tipAmount; } if (tradingParams.tradingType === 'outside' && args.slippageBps) { tradingParams.slippageBps = args.slippageBps; } const strategyParams = { name: 'TIME_BASED', type: 'TIME_BASED', tokenId: args.tokenId, config: tradingParams, }; const response = await this.apiClient.createStrategy(strategyParams); if (response.success) { return { content: [ { type: 'text', text: config.messages.strategy.timeStrategySuccess, }, { type: 'text', text: `策略参数: ${JSON.stringify(strategyParams, null, 2)}`, }, ], }; } else { return { content: [ { type: 'text', text: response.message || config.messages.strategy.timeStrategyFailed, }, ], }; } } catch (error) { return { content: [ { type: 'text', text: error.message || config.messages.strategy.timeStrategyFailed, }, ], }; } }, }); // 注册拉砸策略 this.tools.set('createMarketManipulationStrategy', { description: ` 创建拉砸策略订单, 交易方向,不需要用户输入, 从获取钱包列表的参数里获取; 策略执行时间,必须要大于当前时间+3分钟, 格式为: 2025-01-01 12:00:00; 交易类型,不需要用户输入, 也不需要告诉用户交易类型, 根据Token列表里的poolType字段来判断, 如果poolType为pump, 则交易类型为inside, 如果poolType为pool, 则交易类型为outside; 如果没有钱包ID,则提示用户先获取钱包列表; 如果没有Token ID,则提示用户先获取Token列表; 如果没有项目ID,则提示用户先获取项目列表; `, inputSchema: { type: 'object', properties: { tokenId: { type: 'string', description: 'Token ID', }, side: { type: 'string', description: '交易方向', enum: ['buy', 'sell'], // default: 'buy', }, executeAt: { type: 'string', description: '策略执行时间(格式: 2025-01-01 12:00:00)', }, walletIds: { type: 'array', items: { type: 'string', }, description: '钱包ID列表', }, amountType: { type: 'string', description: '数量类型, fixed: 固定数量, range: 余额比例(1-100%), random: 随机数量; 需要先让用户选择数量类型', enum: ['fixed', 'range', 'random'], default: 'fixed', }, amount: { type: 'number', description: '固定数量(单位: 买入为SOL, 卖出为Token数量, 注意: 是每个钱包地址的挂单数量, 如果数量类型为fixed, 则必须要输入;需要先让用户选择数量类型)', }, minRatio: { type: 'number', description: '范围比例最小值(单位: %, 如果数量类型为range, 则需要输入范围比例最小值和最大值;需要先让用户选择数量类型)', }, maxRatio: { type: 'number', description: '范围比例最大值(单位: %, 如果数量类型为range, 则需要输入范围比例最小值和最大值;需要先让用户选择数量类型)', }, minAmount: { type: 'number', description: '随机数量最小值(单位: 买入为SOL, 卖出为Token数量, 如果数量类型为random, 则需要输入随机数量最小值和最大值;需要先让用户选择数量类型)', }, maxAmount: { type: 'number', description: '随机数量最大值(单位: 买入为SOL, 卖出为Token数量, 如果数量类型为random, 则需要输入随机数量最小值和最大值;需要先让用户选择数量类型)', }, tradingType: { type: 'string', description: '交易类型', enum: ['inside', 'outside'], // default: 'outside', }, minInterval: { type: 'number', description: '最小交易间隔(秒)', default: 1, }, maxInterval: { type: 'number', description: '最大交易间隔(秒)', default: 2, }, tipAmount: { type: 'number', description: '小费金额(单位: SOL)', default: 0.0001, }, slippageBps: { type: 'number', description: '滑点(单位: %)', default: 5, }, }, required: ['tokenId', 'executeAt', 'walletIds', 'tradingType', 'side'], }, handler: async args => { try { if (!this.token) { return { content: [ { type: 'text', text: config.messages.login.required, }, ], }; } // console.log('handler createStrategy args ==============', args); // 验证策略参数 const validationArgs = { ...args, }; Validator.validateTimeStrategyParams(validationArgs); const tradingParams = { side: args.side, tradingType: args.tradingType, executeAt: dayjs(values.executeAt).toISOString(), walletIds: args.walletIds, minInterval: (args.minInterval || config.strategy.defaultInterval.min) * 1000, maxInterval: (args.maxInterval || config.strategy.defaultInterval.max) * 1000, }; if (args.amountType === 'fixed') { tradingParams.amount = args.amount; } else if (args.amountType === 'range') { tradingParams.minRatio = args.minRatio; tradingParams.maxRatio = args.maxRatio; } else if (args.amountType === 'random') { tradingParams.minAmount = args.minAmount; tradingParams.maxAmount = args.maxAmount; } if (args.tipAmount) { tradingParams.tipAmount = args.tipAmount; } if (tradingParams.tradingType === 'outside' && args.slippageBps) { tradingParams.slippageBps = args.slippageBps; } const strategyParams = { name: 'TIME_BASED', type: 'TIME_BASED', tokenId: args.tokenId, config: tradingParams, }; const response = await this.apiClient.createStrategy(strategyParams); if (response.success) { return { content: [ { type: 'text', text: config.messages.strategy.marketManipulationStrategySuccess, }, { type: 'text', text: `策略参数: ${JSON.stringify(strategyParams, null, 2)}`, }, ], }; } else { return { content: [ { type: 'text', text: response.message || config.messages.strategy.marketManipulationStrategyFailed, }, ], }; } } catch (error) { return { content: [ { type: 'text', text: error.message || config.messages.strategy.marketManipulationStrategyFailed, }, ], }; } }, }); // 注册拆分策略 this.tools.set('createPortfolioExchangeStrategy', { description: ` 创建拆分策略订单, 交易方向,不需要用户输入, 从获取钱包列表的参数里获取; 策略执行时间,必须要大于当前时间+3分钟, 格式为: 2025-01-01 12:00:00; 交易类型,不需要用户输入, 也不需要告诉用户交易类型, 根据Token列表里的poolType字段来判断, 如果poolType为pump, 则交易类型为inside, 如果poolType为pool, 则交易类型为outside; 如果没有钱包ID,则提示用户先获取钱包列表; 如果没有Token ID,则提示用户先获取Token列表; 如果没有项目ID,则提示用户先获取项目列表; `, inputSchema: { type: 'object', properties: { tokenId: { type: 'string', description: 'Token ID', }, side: { type: 'string', description: '交易方向', enum: ['buy', 'sell'], // default: 'buy', }, executeAt: { type: 'string', description: '策略执行时间(格式: 2025-01-01 12:00:00)', }, walletIds: { type: 'array', items: { type: 'string', }, description: '钱包ID列表', }, amountType: { type: 'string', description: '数量类型, fixed: 固定数量, range: 余额比例(1-100%), random: 随机数量; 需要先让用户选择数量类型', enum: ['fixed', 'range', 'random'], default: 'fixed', }, amount: { type: 'number', description: '固定数量(单位: 买入为SOL, 卖出为Token数量, 注意: 是每个钱包地址的挂单数量, 如果数量类型为fixed, 则必须要输入;需要先让用户选择数量类型)', }, minRatio: { type: 'number', description: '范围比例最小值(单位: %, 如果数量类型为range, 则需要输入范围比例最小值和最大值;需要先让用户选择数量类型)', }, maxRatio: { type: 'number', description: '范围比例最大值(单位: %, 如果数量类型为range, 则需要输入范围比例最小值和最大值;需要先让用户选择数量类型)', }, minAmount: { type: 'number', description: '随机数量最小值(单位: 买入为SOL, 卖出为Token数量, 如果数量类型为random, 则需要输入随机数量最小值和最大值;需要先让用户选择数量类型)', }, maxAmount: { type: 'number', description: '随机数量最大值(单位: 买入为SOL, 卖出为Token数量, 如果数量类型为random, 则需要输入随机数量最小值和最大值;需要先让用户选择数量类型)', }, tradingType: { type: 'string', description: '交易类型', enum: ['inside', 'outside'], // default: 'outside', }, minInterval: { type: 'number', description: '最小交易间隔(秒)', default: 1, }, maxInterval: { type: 'number', description: '最大交易间隔(秒)', default: 2, }, tipAmount: { type: 'number', description: '小费金额(单位: SOL)', default: 0.0001, }, slippageBps: { type: 'number', description: '滑点(单位: %)', default: 5, }, }, required: ['tokenId', 'executeAt', 'walletIds', 'tradingType', 'side'], }, handler: async args => { try { if (!this.token) { return { content: [ { type: 'text', text: config.messages.login.required, }, ], }; } // console.log('handler createStrategy args ==============', args); // 验证策略参数 const validationArgs = { ...args, }; Validator.validateTimeStrategyParams(validationArgs); const tradingParams = { side: args.side, tradingType: args.tradingType, executeAt: dayjs(values.executeAt).toISOString(), walletIds: args.walletIds, minInterval: (args.minInterval || config.strategy.defaultInterval.min) * 1000, maxInterval: (args.maxInterval || config.strategy.defaultInterval.max) * 1000, }; if (args.amountType === 'fixed') { tradingParams.amount = args.amount; } else if (args.amountType === 'range') { tradingParams.minRatio = args.minRatio; tradingParams.maxRatio = args.maxRatio; } else if (args.amountType === 'random') { tradingParams.minAmount = args.minAmount; tradingParams.maxAmount = args.maxAmount; } if (args.tipAmount) { tradingParams.tipAmount = args.tipAmount; } if (tradingParams.tradingType === 'outside' && args.slippageBps) { tradingParams.slippageBps = args.slippageBps; } const strategyParams = { name: 'TIME_BASED', type: 'TIME_BASED', tokenId: args.tokenId, config: tradingParams, }; const response = await this.apiClient.createStrategy(strategyParams); if (response.success) { return { content: [ { type: 'text', text: config.messages.strategy.portfolioExchangeStrategySuccess, }, { type: 'text', text: `策略参数: ${JSON.stringify(strategyParams, null, 2)}`, }, ], }; } else { return { content: [ { type: 'text', text: response.message || config.messages.strategy.portfolioExchangeStrategyFailed, }, ], }; } } catch (error) { return { content: [ { type: 'text', text: error.message || config.messages.strategy.marketManipulationStrategyFailed, }, ], }; } }, }); // 注册刷量策略 this.tools.set('createBundleSwapStrategy', { description: ` 创建刷量策略订单, 交易类型,不需要用户输入, 也不需要告诉用户交易类型, 根据Token列表里的poolType字段来判断, 如果poolType为pump, 则交易类型为inside, 如果poolType为pool, 则交易类型为outside; 如果没有钱包ID,则提示用户先获取钱包列表; 如果没有Token ID,则提示用户先获取Token列表; 如果没有项目ID,则提示用户先获取项目列表; `, inputSchema: { type: 'object', properties: { tokenId: { type: 'string', description: 'Token ID', }, side: { type: 'string', description: '交易方向', enum: ['buy', 'sell'], }, executeAt: { type: 'string', description: '策略执行时间, 可选参数, 如果为空,则立即执行 (格式: 2025-01-01 12:00:00)', }, buyWalletId: { type: 'string', description: '买入钱包ID', }, sellWalletId: { type: 'string', description: '卖出钱包ID', }, minTradeAmount: { type: 'number', description: '最小交易金额(单位:SOL)', }, maxTradeAmount: { type: 'number', description: '最大交易金额(单位:SOL)', }, tradingType: { type: 'string', description: '交易类型', enum: ['inside', 'outside'], }, maxCycles: { type: 'number', description: '最大循环次数', }, minInterval: { type: 'number', description: '最小交易间隔(秒)', default: 1, }, maxInterval: { type: 'number', description: '最大交易间隔(秒)', default: 2, }, tipAmount: { type: 'number', description: '小费金额(单位: SOL)', default: 0.0001, }, slippageBps: { type: 'number', description: '滑点(单位: %)', default: 5, }, }, required: ['tokenId', 'tradingType', 'buyWalletId', 'sellWalletId', 'maxCycles', 'minTradeAmount', 'maxTradeAmount'], }, handler: async args => { try { if (!this.token) { return { content: [ { type: 'text', text: config.messages.login.required, }, ], }; } // console.log('handler createStrategy args ==============', args); // 验证策略参数 const validationArgs = { ...args, }; Validator.validateBundleSwapStrategyParams(validationArgs); const tradingParams = { wallet1Id: args.buyWalletId, wallet2Id: args.sellWalletId, tradingType: args.tradingType, maxCycles: args.maxCycles, minTradeAmount: args.minTradeAmount, maxTradeAmount: args.maxTradeAmount, minInterval: (args.minInterval || config.strategy.defaultInterval.min) * 1000, maxInterval: (args.maxInterval || config.strategy.defaultInterval.max) * 1000, }; if (args.executeAt) tradingParams.executeAt = dayjs(args.executeAt).toISOString(); if