| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- import { TOKEN_MAP, SMART_WALLET } from './const.js'
- 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) {
- 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}`)
- 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) {
- 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) {
- const success = await sendToDiscord(position, symbol, tokenList)
- 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')
- }, INTERVAL_MS)
- setInterval(processTradePair, MAX_AGE_MS)
|