import { TOKEN_MAP, SMART_WALLET } from './const.js' import { addLiquidity } from './addLiq.mjs' const API_URL = 'https://app-byreal-table.trrlzk.easypanel.host/api/top-positions' const TRADE_PAIR_URL = 'https://app-byreal-table.trrlzk.easypanel.host/api/pools/list?page=1&pageSize=500' const DISCORD_WEBHOOK_URL = 'https://discord.com/api/webhooks/1449354624225120378/7gOkPXzZkaoQF7XWKFZRsevVSKXcUOuim2Rhtx_gWzBd9b7wphFMQiAIqBnRT3Gmkog4' const DISCORD_WEBHOOK_URL_SMART_WALLET = 'https://discord.com/api/webhooks/1450301829064954040/zNYc-mnYPDFgnAPLZxY40aicERwdL4TsvXk9VyiqWgyjRqJMXKIBzVHEvBiHbpbJrmtu' const DEFAULT_POOL_ADDRESS = TOKEN_MAP['MON'] const INTERVAL_MS = 1 * 60 * 1000 // 1分钟 const MAX_AGE_MS = 10 * 60 * 1000 // 10分钟 // 存储已发送的 positionAddress const sentPositions = new Set() const sentTradePair = new Set() let firstRun = true /** * 获取 top-positions 数据 */ async function fetchTopPositions(token_symbol) { try { const response = await fetch(API_URL, { method: 'POST', headers: { 'content-type': 'application/json', }, body: JSON.stringify({ poolAddress: TOKEN_MAP[token_symbol], page: 1, pageSize: 200, sortField: 'liquidity', status: 0, }), }) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } const result = await response.json() if (result.retCode !== 0 || !result.result?.data?.records) { console.error('API 返回错误:', result.retMsg || '未知错误') return [] } const poolMap = Object.values(result.result.data.poolMap)[0] return { records: result.result.data.records, symbol: `${poolMap.mintA.symbol}/${poolMap.mintB.symbol}`, tokenList: [poolMap.mintA.address, poolMap.mintB.address], } } catch (error) { console.error('获取数据失败:', error) return [] } } /** * 获取交易对数据 */ async function fetchTradePair() { try { const response = await fetch(TRADE_PAIR_URL) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } const result = await response.json() if (result.retCode !== 0 || !result.result?.data?.records) { console.error('API 返回错误:', result.retMsg || '未知错误') return [] } return result.result.data.records } catch (error) { console.error('获取交易对数据失败:', error) return [] } } /** * 发送消息到 Discord */ async function sendToDiscord(position, symbol, tokenList, maxUsdValue) { try { const ageMinutes = Math.floor(position.positionAgeMs / 1000 / 60) const ageSeconds = Math.floor((position.positionAgeMs / 1000) % 60) const embed = { title: '🆕 新仓位发现', color: 0x00b098, // 绿色 fields: [ { name: '创建地址', value: `\`${position.walletAddress}\``, inline: true, }, { name: '仓位地址', value: `\`${position.positionAddress}\``, inline: false, }, { name: '地址', value: `https://www.byreal.io/en/portfolio?userAddress=${position.walletAddress}&tab=current&positionAddress=${position.positionAddress}&tokenAddress=${tokenList[0]}&tokenAddress=${tokenList[1]}`, inline: false, }, { name: '交易对', value: `\`${symbol}\``, inline: true, }, { name: '流动性', value: `$${Number(position.liquidityUsd).toFixed(2)}`, inline: true, }, { name: '复制数', value: `${position.copies}`, inline: true, }, { name: '奖励', value: `$${Number(position.bonusUsd).toFixed(2)}`, inline: true, }, { name: 'FEE', value: `$${Number(position.earnedUsd).toFixed(2)}`, inline: true, }, { name: 'PNL', value: `$${Number(position.pnlUsd).toFixed(2)}`, inline: true, }, { name: '创建时间', value: `${ageMinutes}分${ageSeconds}秒`, inline: true, }, { name: 'APR', value: calculateAPR(position), inline: true, }, ], timestamp: new Date().toISOString(), url: `https://www.byreal.io/en/portfolio?userAddress=${position.walletAddress}&tab=current&positionAddress=${position.positionAddress}`, } let DISCORD_WEBHOOK = SMART_WALLET.includes(position.walletAddress) ? DISCORD_WEBHOOK_URL_SMART_WALLET : DISCORD_WEBHOOK_URL if (SMART_WALLET.includes(position.walletAddress)) { embed.title = '💎 发现重点监控仓位' } const response = await fetch(DISCORD_WEBHOOK, { method: 'POST', headers: { 'content-type': 'application/json', }, body: JSON.stringify({ embeds: [embed], }), }) if (!response.ok) { throw new Error(`Discord webhook 失败: ${response.status}`) } console.log(`✅ 已发送到 Discord: ${position.positionAddress}`) if (Number(position.liquidityUsd) > 100) { // 等待5秒再复制流动性 await new Promise((resolve) => setTimeout(resolve, 5000)) await addLiquidity(position, symbol, maxUsdValue) console.log(`✅ 已复制流动性: ${position.positionAddress}`) } else { console.log(`❌ 未复制流动性: ${position.positionAddress}`) } return true } catch (error) { console.error('发送到 Discord 失败:', error) return false } } /** * 发送消息到 Discord 交易对 */ async function sendToDiscordTradePair(pair) { const embed = { title: '🆕 新交易对发现', color: 0x00b098, // 绿色 fields: [ { name: '交易对', value: `${pair.mintA.mintInfo.symbol}/${pair.mintB.mintInfo.symbol}`, inline: true, }, ], timestamp: new Date().toISOString(), url: `https://www.byreal.io/en/portfolio?userAddress=${pair.poolAddress}&tab=current&positionAddress=${pair.poolAddress}`, } try { const response = await fetch(DISCORD_WEBHOOK_URL_SMART_WALLET, { method: 'POST', headers: { 'content-type': 'application/json', }, body: JSON.stringify({ embeds: [embed], }), }) if (!response.ok) { throw new Error(`Discord webhook 失败: ${response.status}`) } console.log(`✅ 已发送到 Discord: ${pair.poolAddress}`) return true } catch (error) { console.error('发送到 Discord 交易对失败:', error) return false } } /** * 计算 APR */ function calculateAPR(position) { const earnedPerSecond = Number(position.earnedUsd) / (Number(position.positionAgeMs) / 1000) const apr = (earnedPerSecond * 60 * 60 * 24 * 365) / Number(position.liquidityUsd) return `${(apr * 100).toFixed(2)}%` } /** * 处理新仓位 */ async function processPositions(token_symbol, maxUsdValue) { console.log(`\n[${new Date().toLocaleString('zh-CN')}] 开始检查新仓位...`) const { records, symbol, tokenList } = await fetchTopPositions(token_symbol) if (!records) { console.log('获取数据失败') return } // 过滤出10分钟以内的仓位 const newPositions = records.filter((record) => { const ageMs = Number(record.positionAgeMs) return ageMs < MAX_AGE_MS && ageMs >= 0 }) console.log(`找到 ${newPositions.length} 个10分钟内的仓位`) // 过滤出未发送过的仓位 const unsentPositions = newPositions.filter((position) => { const address = position.positionAddress return address && !sentPositions.has(address) }) console.log(`其中 ${unsentPositions.length} 个未发送过`) // 发送到 Discord for (const position of unsentPositions) { const address = position.positionAddress if (address && Number(position.liquidityUsd) > 10) { const success = await sendToDiscord(position, symbol, tokenList, maxUsdValue) if (success) { sentPositions.add(address) } // 避免发送过快,稍微延迟 await new Promise((resolve) => setTimeout(resolve, 500)) } } console.log(`处理完成,已发送 ${unsentPositions.length} 个新仓位`) } /** * 处理交易对数据 */ async function processTradePair() { console.log(`\n[${new Date().toLocaleString('zh-CN')}] 开始检查交易对...`) const tradePair = await fetchTradePair() if (!tradePair) { console.log('获取交易对数据失败') return } console.log(`找到 ${tradePair.length} 个交易对`) // 过滤出未发送过的交易对 const unsentTradePair = tradePair.filter((pair) => { return pair.poolAddress && !sentTradePair.has(pair.poolAddress) }) console.log(`其中 ${unsentTradePair.length} 个未发送过`) if (!firstRun) { // 发送到 Discord for (const pair of unsentTradePair) { const success = await sendToDiscordTradePair(pair) if (success) { sentTradePair.add(pair.poolAddress) } // 避免发送过快,稍微延迟 await new Promise((resolve) => setTimeout(resolve, 500)) } console.log(`处理完成,已发送 ${unsentTradePair.length} 个交易对`) } else { firstRun = false for (const pair of tradePair) { sentTradePair.add(pair.poolAddress) } console.log('第一次运行,不发送交易对') } } // 启动定时任务 console.log('🚀 监控程序已启动') console.log(`- 检查间隔: ${INTERVAL_MS / 1000 / 60} 分钟`) console.log(`- 监控池地址: ${DEFAULT_POOL_ADDRESS}`) console.log(`- 最大年龄: ${MAX_AGE_MS / 1000 / 60} 分钟`) console.log(`- Discord Webhook: ${DISCORD_WEBHOOK_URL}`) // 立即执行一次 // processPositions('MON') // processPositions('WET') processTradePair() // 每5分钟执行一次 setInterval(() => { // processPositions('MON') // processPositions('WET') // processPositions('WhiteWhale', 3) // processPositions('fish', 1) }, INTERVAL_MS) setInterval(processTradePair, MAX_AGE_MS)