UNPKG

@elizaos/plugin-dexscreener

Version:

DexScreener integration plugin for ElizaOS

947 lines (937 loc) 31.1 kB
// src/service.ts import { Service } from "@elizaos/core"; import axios from "axios"; var DexScreenerService = class _DexScreenerService extends Service { static serviceType = "dexscreener"; api; dexConfig; lastRequestTime = 0; capabilityDescription = "Provides DEX analytics and token information from DexScreener"; /** * Start the DexScreenerService with the given runtime. * @param {IAgentRuntime} runtime - The runtime for the DexScreenerService. * @returns {Promise<Service>} A promise that resolves with the DexScreenerService instance. */ static async start(runtime) { const service = new _DexScreenerService(runtime); service.dexConfig = { apiUrl: runtime.getSetting("DEXSCREENER_API_URL") || "https://api.dexscreener.com", rateLimitDelay: parseInt( runtime.getSetting("DEXSCREENER_RATE_LIMIT_DELAY") || "100" ) }; service.api = axios.create({ baseURL: service.dexConfig.apiUrl, timeout: 1e4, headers: { Accept: "application/json", "User-Agent": "ElizaOS-DexScreener-Plugin/1.0" } }); return service; } async stop() { console.log("DexScreener service stopped"); } /** * Ensure rate limiting between requests */ async rateLimit() { const now = Date.now(); const timeSinceLastRequest = now - this.lastRequestTime; if (timeSinceLastRequest < this.dexConfig.rateLimitDelay) { await new Promise( (resolve) => setTimeout( resolve, this.dexConfig.rateLimitDelay - timeSinceLastRequest ) ); } this.lastRequestTime = Date.now(); } /** * Search for tokens/pairs */ async search(params) { try { await this.rateLimit(); const response = await this.api.get(`/latest/dex/search`, { params: { q: params.query } }); return { success: true, data: response.data.pairs || [] }; } catch (error) { console.error("DexScreener search error:", error); return { success: false, error: error.response?.data?.message || error.message || "Failed to search tokens" }; } } /** * Get token pairs by token address */ async getTokenPairs(params) { try { await this.rateLimit(); const response = await this.api.get( `/latest/dex/tokens/${params.tokenAddress}` ); return { success: true, data: response.data.pairs || [] }; } catch (error) { console.error("DexScreener getTokenPairs error:", error); return { success: false, error: error.response?.data?.message || error.message || "Failed to get token pairs" }; } } /** * Get pair by address */ async getPair(params) { try { await this.rateLimit(); const response = await this.api.get( `/latest/dex/pairs/${params.pairAddress}` ); if (!response.data.pair) { return { success: false, error: "Pair not found" }; } return { success: true, data: response.data.pair }; } catch (error) { console.error("DexScreener getPair error:", error); return { success: false, error: error.response?.data?.message || error.message || "Failed to get pair" }; } } /** * Get trending pairs */ async getTrending(params = {}) { try { await this.rateLimit(); const response = await this.api.get(`/token-boosts/top/v1`); const boostedTokens = Array.isArray(response.data) ? response.data : [response.data]; const pairPromises = boostedTokens.slice(0, params.limit || 10).map(async (token) => { try { const pairResponse = await this.api.get( `/tokens/v1/${token.chainId}/${token.tokenAddress}` ); return Array.isArray(pairResponse.data) ? pairResponse.data[0] : null; } catch (error) { console.error( `Failed to get pair data for ${token.tokenAddress}:`, error ); return null; } }); const pairs = (await Promise.all(pairPromises)).filter( (pair) => pair !== null ); return { success: true, data: pairs }; } catch (error) { console.error("DexScreener getTrending error:", error); return { success: false, error: error.response?.data?.message || error.message || "Failed to get trending pairs" }; } } /** * Get pairs by chain */ async getPairsByChain(params) { try { await this.rateLimit(); const response = await this.api.get(`/latest/dex/search`, { params: { q: params.chain // Search by chain name } }); let pairs = response.data.pairs || []; pairs = pairs.filter( (pair) => pair.chainId.toLowerCase() === params.chain.toLowerCase() ); if (params.sortBy) { pairs.sort((a, b) => { switch (params.sortBy) { case "volume": return (b.volume?.h24 || 0) - (a.volume?.h24 || 0); case "liquidity": return (b.liquidity?.usd || 0) - (a.liquidity?.usd || 0); case "priceChange": return (b.priceChange?.h24 || 0) - (a.priceChange?.h24 || 0); case "txns": return (b.txns?.h24.buys + b.txns?.h24.sells || 0) - (a.txns?.h24.buys + a.txns?.h24.sells || 0); default: return 0; } }); } const limitedPairs = params.limit ? pairs.slice(0, params.limit) : pairs.slice(0, 20); return { success: true, data: limitedPairs }; } catch (error) { console.error("DexScreener getPairsByChain error:", error); return { success: false, error: error.response?.data?.message || error.message || "Failed to get pairs by chain" }; } } /** * Get new pairs */ async getNewPairs(params = {}) { try { await this.rateLimit(); const response = await this.api.get(`/token-profiles/latest/v1`); const profiles = Array.isArray(response.data) ? response.data : [response.data]; const filteredProfiles = params.chain ? profiles.filter( (p) => p.chainId?.toLowerCase() === params.chain?.toLowerCase() ) : profiles; const pairPromises = filteredProfiles.slice(0, params.limit || 10).map(async (profile) => { try { const pairResponse = await this.api.get( `/tokens/v1/${profile.chainId}/${profile.tokenAddress}` ); const pairs2 = Array.isArray(pairResponse.data) ? pairResponse.data : []; if (pairs2.length > 0) { return { ...pairs2[0], labels: pairs2[0].labels?.includes("new") ? pairs2[0].labels : [...pairs2[0].labels || [], "new"] }; } return null; } catch (error) { console.error( `Failed to get pair data for ${profile.tokenAddress}:`, error ); return null; } }); const pairs = (await Promise.all(pairPromises)).filter( (pair) => pair !== null ); return { success: true, data: pairs }; } catch (error) { console.error("DexScreener getNewPairs error:", error); return { success: false, error: error.response?.data?.message || error.message || "Failed to get new pairs" }; } } /** * Get token profile */ async getTokenProfile(tokenAddress) { try { await this.rateLimit(); const response = await this.api.get(`/token-profiles/latest/v1`); const profiles = Array.isArray(response.data) ? response.data : [response.data]; const profile = profiles.find( (p) => p.tokenAddress?.toLowerCase() === tokenAddress.toLowerCase() ); if (!profile) { return { success: false, error: "Token profile not found" }; } return { success: true, data: profile }; } catch (error) { console.error("DexScreener getTokenProfile error:", error); return { success: false, error: error.response?.data?.message || error.message || "Failed to get token profile" }; } } /** * Format price with appropriate decimals */ formatPrice(price) { const numPrice = typeof price === "string" ? parseFloat(price) : price; if (numPrice >= 1) { return numPrice.toFixed(2); } else if (numPrice >= 0.01) { return numPrice.toFixed(4); } else { return numPrice.toFixed(8); } } /** * Format percentage change */ formatPriceChange(change) { const sign = change >= 0 ? "+" : ""; return `${sign}${change.toFixed(2)}%`; } /** * Format volume/liquidity numbers */ formatUsdValue(value) { if (value >= 1e6) { return `$${(value / 1e6).toFixed(2)}M`; } else if (value >= 1e3) { return `$${(value / 1e3).toFixed(2)}K`; } else { return `$${value.toFixed(2)}`; } } /** * Get multiple tokens by addresses (up to 30) */ async getMultipleTokens(chainId, tokenAddresses) { try { if (tokenAddresses.length > 30) { return { success: false, error: "Maximum 30 token addresses allowed" }; } await this.rateLimit(); const addresses = tokenAddresses.join(","); const response = await this.api.get(`/tokens/v1/${chainId}/${addresses}`); return { success: true, data: response.data || [] }; } catch (error) { console.error("DexScreener getMultipleTokens error:", error); return { success: false, error: error.response?.data?.message || error.message || "Failed to get multiple tokens" }; } } /** * Get latest token profiles */ async getLatestTokenProfiles() { try { await this.rateLimit(); const response = await this.api.get(`/token-profiles/latest/v1`); return { success: true, data: Array.isArray(response.data) ? response.data : [response.data] }; } catch (error) { console.error("DexScreener getLatestTokenProfiles error:", error); return { success: false, error: error.response?.data?.message || error.message || "Failed to get latest token profiles" }; } } /** * Get latest boosted tokens */ async getLatestBoostedTokens() { try { await this.rateLimit(); const response = await this.api.get(`/token-boosts/latest/v1`); return { success: true, data: Array.isArray(response.data) ? response.data : [response.data] }; } catch (error) { console.error("DexScreener getLatestBoostedTokens error:", error); return { success: false, error: error.response?.data?.message || error.message || "Failed to get latest boosted tokens" }; } } /** * Get top boosted tokens */ async getTopBoostedTokens() { try { await this.rateLimit(); const response = await this.api.get(`/token-boosts/top/v1`); return { success: true, data: Array.isArray(response.data) ? response.data : [response.data] }; } catch (error) { console.error("DexScreener getTopBoostedTokens error:", error); return { success: false, error: error.response?.data?.message || error.message || "Failed to get top boosted tokens" }; } } /** * Check order status for a token */ async checkOrderStatus(chainId, tokenAddress) { try { await this.rateLimit(); const response = await this.api.get( `/orders/v1/${chainId}/${tokenAddress}` ); return { success: true, data: response.data || [] }; } catch (error) { console.error("DexScreener checkOrderStatus error:", error); return { success: false, error: error.response?.data?.message || error.message || "Failed to check order status" }; } } /** * Get token pairs by chain and address */ async getTokenPairsByChain(chainId, tokenAddress) { try { await this.rateLimit(); const response = await this.api.get( `/token-pairs/v1/${chainId}/${tokenAddress}` ); return { success: true, data: response.data || [] }; } catch (error) { console.error("DexScreener getTokenPairsByChain error:", error); return { success: false, error: error.response?.data?.message || error.message || "Failed to get token pairs by chain" }; } } }; // src/actions.ts var searchTokensAction = { name: "DEXSCREENER_SEARCH", description: "Search for tokens or trading pairs on DexScreener by name, symbol, or contract address", validate: async (runtime, message) => { const content = typeof message.content === "string" ? message.content : message.content?.text || ""; return content.toLowerCase().includes("search") || content.toLowerCase().includes("find") || content.toLowerCase().includes("look for"); }, handler: async (runtime, message, _, __, callback) => { if (!callback) { console.error("No callback"); return; } const service = runtime.getService("dexscreener"); const content = typeof message.content === "string" ? message.content : message.content?.text || ""; const queryMatch = content.match( /(?:search|find|look for)\s+(?:for\s+)?(.+?)(?:\s+on\s+dexscreener)?$/i ); if (!queryMatch) { callback({ text: 'Please provide a search query. Example: "Search for PEPE"', action: "DEXSCREENER_SEARCH" }); return; } const result = await service.search({ query: queryMatch[1].trim() }); if (!result.success || !result.data) { callback({ text: `Failed to search: ${result.error}`, action: "DEXSCREENER_SEARCH" }); return; } const pairs = result.data.slice(0, 5); if (pairs.length === 0) { callback({ text: `No results found for "${queryMatch[1].trim()}"`, action: "DEXSCREENER_SEARCH" }); return; } const pairList = pairs.map((pair, i) => { const priceChange = service.formatPriceChange(pair.priceChange.h24); return `**${i + 1}. ${pair.baseToken.symbol}/${pair.quoteToken.symbol}** on ${pair.dexId} (${pair.chainId}) \u{1F4B0} Price: ${service.formatPrice(pair.priceUsd || pair.priceNative)} \u{1F4C8} 24h: ${priceChange} | Vol: ${service.formatUsdValue(pair.volume.h24)} \u{1F4A7} Liq: ${pair.liquidity?.usd ? service.formatUsdValue(pair.liquidity.usd) : "N/A"} \u{1F517} ${pair.url}`; }).join("\n\n"); callback({ text: `**\u{1F50D} Search Results for "${queryMatch[1].trim()}"** ${pairList}`, action: "DEXSCREENER_SEARCH", data: pairs }); return; }, similes: ["find token", "look for", "search dexscreener"], examples: [ [ { name: "Alice", content: { text: "Search for PEPE tokens" } }, { name: "Bob", content: { text: "Find USDC pairs on dexscreener" } } ] ] }; var getTokenInfoAction = { name: "DEXSCREENER_TOKEN_INFO", description: "Get detailed information about a specific token including price, volume, liquidity, and trading pairs from DexScreener", validate: async (runtime, message) => { const content = typeof message.content === "string" ? message.content : message.content?.text || ""; return content.toLowerCase().includes("token") && (content.toLowerCase().includes("info") || content.toLowerCase().includes("details") || content.toLowerCase().includes("price")); }, handler: async (runtime, message, _, __, callback) => { if (!callback) { console.error("No callback"); return; } const service = runtime.getService("dexscreener"); const content = typeof message.content === "string" ? message.content : message.content?.text || ""; const addressMatch = content.match(/0x[a-fA-F0-9]{40}/); if (!addressMatch) { callback({ text: 'Please provide a token address. Example: "Get token info for 0x..."', action: "DEXSCREENER_TOKEN_INFO" }); return; } const result = await service.getTokenPairs({ tokenAddress: addressMatch[0] }); if (!result.success || !result.data) { callback({ text: `Failed to get token info: ${result.error}`, action: "DEXSCREENER_TOKEN_INFO" }); return; } const pairs = result.data; if (pairs.length === 0) { callback({ text: `No pairs found for token ${addressMatch[0]}`, action: "DEXSCREENER_TOKEN_INFO" }); return; } const mainPair = pairs.reduce( (prev, curr) => (curr.liquidity?.usd || 0) > (prev.liquidity?.usd || 0) ? curr : prev ); const pairList = pairs.slice(0, 3).map( (pair) => `\u2022 **${pair.baseToken.symbol}/${pair.quoteToken.symbol}** on ${pair.dexId} (${pair.chainId}) Price: ${service.formatPrice(pair.priceUsd || pair.priceNative)} | Liq: ${pair.liquidity?.usd ? service.formatUsdValue(pair.liquidity.usd) : "N/A"}` ).join("\n"); callback({ text: `**\u{1F4CA} Token Information** **Token:** ${mainPair.baseToken.name} (${mainPair.baseToken.symbol}) **Address:** \`${mainPair.baseToken.address}\` **Price:** ${service.formatPrice(mainPair.priceUsd || mainPair.priceNative)} **24h Change:** ${service.formatPriceChange(mainPair.priceChange.h24)} **24h Volume:** ${service.formatUsdValue(mainPair.volume.h24)} **Market Cap:** ${mainPair.marketCap ? service.formatUsdValue(mainPair.marketCap) : "N/A"} **FDV:** ${mainPair.fdv ? service.formatUsdValue(mainPair.fdv) : "N/A"} **Top Trading Pairs:** ${pairList}`, action: "DEXSCREENER_TOKEN_INFO", data: pairs }); return; }, similes: ["token details", "token price", "get token", "check token"], examples: [ [ { name: "Alice", content: { text: "Get token info for 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" } }, { name: "Bob", content: { text: "What is the price of token 0x..." } } ] ] }; var getTrendingAction = { name: "DEXSCREENER_TRENDING", description: "Get trending tokens from DexScreener based on volume, price changes, and trading activity", validate: async (runtime, message) => { const content = typeof message.content === "string" ? message.content : message.content?.text || ""; return content.toLowerCase().includes("trending") || content.toLowerCase().includes("hot") || content.toLowerCase().includes("popular") || content.toLowerCase().includes("gainers"); }, handler: async (runtime, message, _, __, callback) => { if (!callback) { console.error("No callback"); return; } const service = runtime.getService("dexscreener"); const content = typeof message.content === "string" ? message.content : message.content?.text || ""; const timeframeMatch = content.match(/\b(1h|6h|24h)\b/); const limitMatch = content.match(/top\s+(\d+)/i); const result = await service.getTrending({ timeframe: timeframeMatch?.[1] || "24h", limit: limitMatch ? parseInt(limitMatch[1]) : 10 }); if (!result.success || !result.data) { callback({ text: `Failed to get trending tokens: ${result.error}`, action: "DEXSCREENER_TRENDING" }); return; } const pairs = result.data; const trendingList = pairs.map((pair, i) => { const priceChange = service.formatPriceChange(pair.priceChange.h24); return `**${i + 1}. ${pair.baseToken.symbol}/${pair.quoteToken.symbol}** \u{1F4B0} ${service.formatPrice(pair.priceUsd || pair.priceNative)} (${priceChange}) \u{1F4CA} Vol: ${service.formatUsdValue(pair.volume.h24)} | MCap: ${pair.marketCap ? service.formatUsdValue(pair.marketCap) : "N/A"} \u{1F525} Buys: ${pair.txns.h24.buys} | Sells: ${pair.txns.h24.sells}`; }).join("\n\n"); callback({ text: `**\u{1F525} Trending Tokens (${timeframeMatch?.[1] || "24h"})** ${trendingList}`, action: "DEXSCREENER_TRENDING", data: pairs }); return; }, similes: ["hot tokens", "popular coins", "top gainers", "what's trending"], examples: [ [ { name: "Alice", content: { text: "Show me trending tokens on DexScreener" } }, { name: "Bob", content: { text: "What are the top 5 hot tokens in the last 6h?" } } ] ] }; var getNewPairsAction = { name: "DEXSCREENER_NEW_PAIRS", description: "Get newly created trading pairs from DexScreener, showing recently launched tokens and their initial liquidity", validate: async (runtime, message) => { const content = typeof message.content === "string" ? message.content : message.content?.text || ""; return content.toLowerCase().includes("new") && (content.toLowerCase().includes("pairs") || content.toLowerCase().includes("tokens") || content.toLowerCase().includes("listings")); }, handler: async (runtime, message, _, __, callback) => { if (!callback) { console.error("No callback"); return; } const service = runtime.getService("dexscreener"); const content = typeof message.content === "string" ? message.content : message.content?.text || ""; const chainMatch = content.match(/on\s+(\w+)/i); const limitMatch = content.match(/(\d+)\s+(?:new|latest)/i); const result = await service.getNewPairs({ chain: chainMatch?.[1], limit: limitMatch ? parseInt(limitMatch[1]) : 10 }); if (!result.success || !result.data) { callback({ text: `Failed to get new pairs: ${result.error}`, action: "DEXSCREENER_NEW_PAIRS" }); return; } const pairs = result.data; const newPairsList = pairs.map((pair, i) => { const age = pair.pairCreatedAt ? `${Math.floor((Date.now() - pair.pairCreatedAt) / 6e4)} mins ago` : "Unknown"; return `**${i + 1}. ${pair.baseToken.symbol}/${pair.quoteToken.symbol}** ${pair.labels?.includes("new") ? "\u{1F195}" : ""} \u23F0 Created: ${age} on ${pair.dexId} (${pair.chainId}) \u{1F4B0} Price: ${service.formatPrice(pair.priceUsd || pair.priceNative)} \u{1F4A7} Liquidity: ${pair.liquidity?.usd ? service.formatUsdValue(pair.liquidity.usd) : "N/A"}`; }).join("\n\n"); callback({ text: `**\u{1F195} New Trading Pairs${chainMatch ? ` on ${chainMatch[1]}` : ""}** ${newPairsList}`, action: "DEXSCREENER_NEW_PAIRS", data: pairs }); return; }, similes: ["new listings", "latest pairs", "new tokens", "fresh pairs"], examples: [ [ { name: "Alice", content: { text: "Show me new pairs on DexScreener" } }, { name: "Bob", content: { text: "What are the 5 new tokens on ethereum?" } } ] ] }; var getPairsByChainAction = { name: "DEXSCREENER_CHAIN_PAIRS", description: "Get top trading pairs from a specific blockchain sorted by volume, liquidity, price change, or transaction count", validate: async (runtime, message) => { const content = typeof message.content === "string" ? message.content : message.content?.text || ""; const chains = [ "ethereum", "bsc", "polygon", "arbitrum", "optimism", "base", "solana", "avalanche" ]; return chains.some((chain) => content.toLowerCase().includes(chain)); }, handler: async (runtime, message, _, __, callback) => { if (!callback) { console.error("No callback"); return; } const service = runtime.getService("dexscreener"); const content = typeof message.content === "string" ? message.content : message.content?.text || ""; const chains = [ "ethereum", "bsc", "polygon", "arbitrum", "optimism", "base", "solana", "avalanche" ]; const chain = chains.find((c) => content.toLowerCase().includes(c)); if (!chain) { callback({ text: "Please specify a blockchain. Supported: ethereum, bsc, polygon, arbitrum, optimism, base, solana, avalanche", action: "DEXSCREENER_CHAIN_PAIRS" }); return; } let sortBy = "volume"; if (content.toLowerCase().includes("liquid")) sortBy = "liquidity"; else if (content.toLowerCase().includes("gain") || content.toLowerCase().includes("change")) sortBy = "priceChange"; else if (content.toLowerCase().includes("active") || content.toLowerCase().includes("trades")) sortBy = "txns"; const result = await service.getPairsByChain({ chain, sortBy, limit: 10 }); if (!result.success || !result.data) { callback({ text: `Failed to get ${chain} pairs: ${result.error}`, action: "DEXSCREENER_CHAIN_PAIRS" }); return; } const pairs = result.data; const pairsList = pairs.slice(0, 5).map((pair, i) => { const metric = sortBy === "volume" ? `Vol: ${service.formatUsdValue(pair.volume.h24)}` : sortBy === "liquidity" ? `Liq: ${pair.liquidity?.usd ? service.formatUsdValue(pair.liquidity.usd) : "N/A"}` : sortBy === "priceChange" ? `24h: ${service.formatPriceChange(pair.priceChange.h24)}` : `Trades: ${pair.txns.h24.buys + pair.txns.h24.sells}`; return `**${i + 1}. ${pair.baseToken.symbol}/${pair.quoteToken.symbol}** on ${pair.dexId} \u{1F4B0} ${service.formatPrice(pair.priceUsd || pair.priceNative)} | ${metric}`; }).join("\n\n"); callback({ text: `**\u26D3\uFE0F Top ${chain.charAt(0).toUpperCase() + chain.slice(1)} Pairs by ${sortBy}** ${pairsList}`, action: "DEXSCREENER_CHAIN_PAIRS", data: pairs }); return; }, similes: ["tokens on", "pairs on", "top on"], examples: [ [ { name: "Alice", content: { text: "Show me top tokens on ethereum" } }, { name: "Bob", content: { text: "What are the most liquid pairs on polygon?" } } ] ] }; var getBoostedTokensAction = { name: "DEXSCREENER_BOOSTED_TOKENS", description: "Get boosted (promoted/sponsored) tokens from DexScreener, showing tokens with paid promotional boosts", validate: async (runtime, message) => { const content = typeof message.content === "string" ? message.content : message.content?.text || ""; return content.toLowerCase().includes("boosted") || content.toLowerCase().includes("promoted") || content.toLowerCase().includes("sponsored"); }, handler: async (runtime, message, _, __, callback) => { if (!callback) { console.error("No callback"); return; } const service = runtime.getService("dexscreener"); const content = typeof message.content === "string" ? message.content : message.content?.text || ""; const isTop = content.toLowerCase().includes("top"); const result = isTop ? await service.getTopBoostedTokens() : await service.getLatestBoostedTokens(); if (!result.success || !result.data) { callback({ text: `Failed to get boosted tokens: ${result.error}`, action: "DEXSCREENER_BOOSTED_TOKENS" }); return; } const tokens = result.data.slice(0, 10); if (tokens.length === 0) { callback({ text: "No boosted tokens found", action: "DEXSCREENER_BOOSTED_TOKENS" }); return; } const tokenList = tokens.map((token, i) => { return `**${i + 1}. ${token.tokenAddress}** on ${token.chainId} \u{1F4B0} Boost Amount: ${token.amount} (Total: ${token.totalAmount}) \u{1F4DD} ${token.description || "No description"} \u{1F517} ${token.url}`; }).join("\n\n"); callback({ text: `**\u26A1 ${isTop ? "Top" : "Latest"} Boosted Tokens** ${tokenList}`, action: "DEXSCREENER_BOOSTED_TOKENS", data: tokens }); return; }, similes: ["promoted tokens", "sponsored tokens", "boosted coins"], examples: [ [ { name: "Alice", content: { text: "Show me boosted tokens on DexScreener" } }, { name: "Bob", content: { text: "What are the top promoted tokens?" } } ] ] }; var getTokenProfilesAction = { name: "DEXSCREENER_TOKEN_PROFILES", description: "Get latest token profiles from DexScreener including social links, descriptions, and project information", validate: async (runtime, message) => { const content = typeof message.content === "string" ? message.content : message.content?.text || ""; return content.toLowerCase().includes("profile") && content.toLowerCase().includes("token"); }, handler: async (runtime, message, _, __, callback) => { if (!callback) { console.error("No callback"); return; } const service = runtime.getService("dexscreener"); const result = await service.getLatestTokenProfiles(); if (!result.success || !result.data) { callback({ text: `Failed to get token profiles: ${result.error}`, action: "DEXSCREENER_TOKEN_PROFILES" }); return; } const profiles = result.data.slice(0, 5); if (profiles.length === 0) { callback({ text: "No token profiles found", action: "DEXSCREENER_TOKEN_PROFILES" }); return; } const profileList = profiles.map((profile, i) => { const links = profile.links?.map((l) => `[${l.label}](${l.url})`).join(" | ") || "No links"; return `**${i + 1}. ${profile.tokenAddress}** on ${profile.chainId} \u{1F4DD} ${profile.description || "No description"} \u{1F517} Links: ${links} \u{1F310} ${profile.url}`; }).join("\n\n"); callback({ text: `**\u{1F4CB} Latest Token Profiles** ${profileList}`, action: "DEXSCREENER_TOKEN_PROFILES", data: profiles }); return; }, similes: ["token profiles", "token details page"], examples: [ [ { name: "Alice", content: { text: "Show me latest token profiles" } } ] ] }; var dexscreenerActions = [ searchTokensAction, getTokenInfoAction, getTrendingAction, getNewPairsAction, getPairsByChainAction, getBoostedTokensAction, getTokenProfilesAction ]; // src/index.ts var dexscreenerPlugin = { name: "dexscreener-analytics-plugin", description: "Plugin for DexScreener DEX analytics and token information", actions: dexscreenerActions, evaluators: [], providers: [], services: [DexScreenerService], init: async (_, runtime) => { console.log("DexScreener plugin initialized"); } }; var index_default = dexscreenerPlugin; export { DexScreenerService, index_default as default, dexscreenerActions, dexscreenerPlugin, getBoostedTokensAction, getNewPairsAction, getPairsByChainAction, getTokenInfoAction, getTokenProfilesAction, getTrendingAction, searchTokensAction }; //# sourceMappingURL=index.js.map