UNPKG

@elizaos/plugin-coingecko

Version:

CoinGecko plugin for ElizaOS - provides cryptocurrency market data, prices, and on-chain analytics

1,526 lines (1,464 loc) 81.7 kB
// src/index.ts import { toV2ActionExample } from "@elizaos/core/v1"; // src/actions/getMarkets.ts import { logger as logger2, ModelType } from "@elizaos/core"; import axios2 from "axios"; import { z as z2 } from "zod"; // src/environment.ts import { z } from "zod"; var coingeckoConfigSchema = z.object({ COINGECKO_API_KEY: z.string().nullable(), COINGECKO_PRO_API_KEY: z.string().nullable() }).refine((data) => data.COINGECKO_API_KEY || data.COINGECKO_PRO_API_KEY, { message: "Either COINGECKO_API_KEY or COINGECKO_PRO_API_KEY must be provided" }); async function validateCoingeckoConfig(runtime) { const config = { COINGECKO_API_KEY: runtime.getSetting("COINGECKO_API_KEY"), COINGECKO_PRO_API_KEY: runtime.getSetting("COINGECKO_PRO_API_KEY") }; return coingeckoConfigSchema.parse(config); } function getApiConfig(config) { const isPro = !!config.COINGECKO_PRO_API_KEY; return { baseUrl: isPro ? "https://pro-api.coingecko.com/api/v3" : "https://api.coingecko.com/api/v3", apiKey: isPro ? config.COINGECKO_PRO_API_KEY : config.COINGECKO_API_KEY, headerKey: isPro ? "x-cg-pro-api-key" : "x-cg-demo-api-key" }; } // src/providers/categoriesProvider.ts import { logger } from "@elizaos/core"; import axios from "axios"; var CACHE_TTL = 5 * 60; var MAX_RETRIES = 3; async function fetchCategories(runtime) { var _a; const config = await validateCoingeckoConfig(runtime); const { baseUrl, apiKey, headerKey } = getApiConfig(config); const response = await axios.get( `${baseUrl}/coins/categories/list`, { headers: { accept: "application/json", [headerKey]: apiKey }, timeout: 5e3 // 5 second timeout } ); if (!((_a = response.data) == null ? void 0 : _a.length)) { throw new Error("Invalid categories data received"); } return response.data; } async function fetchWithRetry(runtime) { let lastError = null; for (let i = 0; i < MAX_RETRIES; i++) { try { return await fetchCategories(runtime); } catch (error) { lastError = error; logger.error(`Categories fetch attempt ${i + 1} failed:`, error); await new Promise((resolve) => setTimeout(resolve, 1e3 * (i + 1))); } } throw lastError || new Error("Failed to fetch categories after multiple attempts"); } var cache = {}; async function getCategories(runtime) { try { if (cache.data && cache.timestamp && Date.now() - cache.timestamp < CACHE_TTL * 1e3) { return cache.data; } const categories = await fetchWithRetry(runtime); cache.data = categories; cache.timestamp = Date.now(); return categories; } catch (error) { logger.error("Error fetching categories:", error); throw error; } } function formatCategoriesContext(categories) { const popularCategories = [ "layer-1", "defi", "meme", "ai-meme-coins", "artificial-intelligence", "gaming", "metaverse" ]; const popular = categories.filter((c) => popularCategories.includes(c.category_id)).map((c) => `${c.name} (${c.category_id})`); return ` Available cryptocurrency categories: Popular categories: ${popular.map((c) => `- ${c}`).join("\n")} Total available categories: ${categories.length} You can use these category IDs when filtering cryptocurrency market data. `.trim(); } var categoriesProvider = { name: "CATEGORIES_PROVIDER", // eslint-disable-next-line get: async (runtime, message, state) => { try { const categories = await getCategories(runtime); return { text: formatCategoriesContext(categories), data: { categories } }; } catch (error) { logger.error("Categories provider error:", error); return { text: "Cryptocurrency categories are temporarily unavailable. Please try again later." }; } } }; async function getCategoriesData(runtime) { return getCategories(runtime); } // src/templates/markets.ts var getMarketsTemplate = ` Extract the following parameters for market listing: - **vs_currency** (string): Target currency for price data (default: "usd") - **category** (string, optional): Specific category ID from the available categories - **per_page** (number): Number of results to return (1-250, default: 20) - **order** (string): Sort order for results, one of: - market_cap_desc: Highest market cap first - market_cap_asc: Lowest market cap first - volume_desc: Highest volume first - volume_asc: Lowest volume first Available Categories: {{categories}} Provide the values in the following JSON format: \`\`\`json { "vs_currency": "<currency>", "category": "<category_id>", "per_page": <number>, "order": "<sort_order>", "page": 1, "sparkline": false } \`\`\` Example request: "Show me the top 10 gaming cryptocurrencies" Example response: \`\`\`json { "vs_currency": "usd", "category": "gaming", "per_page": 10, "order": "market_cap_desc", "page": 1, "sparkline": false } \`\`\` Example request: "What are the best performing coins by volume?" Example response: \`\`\`json { "vs_currency": "usd", "per_page": 20, "order": "volume_desc", "page": 1, "sparkline": false } \`\`\` Here are the recent user messages for context: {{recentMessages}} Based on the conversation above, if the request is for a market listing/ranking, extract the appropriate parameters and respond with a JSON object. If the request is for specific coins only, respond with null.`; // src/actions/getMarkets.ts function formatCategory(category, categories) { if (!category) return void 0; const normalizedInput = category.toLowerCase().trim(); const exactMatch = categories.find((c) => c.category_id === normalizedInput); if (exactMatch) { return exactMatch.category_id; } const nameMatch = categories.find( (c) => c.name.toLowerCase() === normalizedInput || c.name.toLowerCase().replace(/[^a-z0-9]+/g, "-") === normalizedInput ); if (nameMatch) { return nameMatch.category_id; } const partialMatch = categories.find( (c) => c.name.toLowerCase().includes(normalizedInput) || c.category_id.includes(normalizedInput) ); if (partialMatch) { return partialMatch.category_id; } return void 0; } var GetMarketsSchema = z2.object({ vs_currency: z2.string().default("usd"), category: z2.string().optional(), order: z2.enum(["market_cap_desc", "market_cap_asc", "volume_desc", "volume_asc"]).default("market_cap_desc"), per_page: z2.number().min(1).max(250).default(20), page: z2.number().min(1).default(1), sparkline: z2.boolean().default(false) }); var isGetMarketsContent = (obj) => { return GetMarketsSchema.safeParse(obj).success; }; var getMarkets_default = { name: "GET_MARKETS", similes: [ "MARKET_OVERVIEW", "TOP_RANKINGS", "MARKET_LEADERBOARD", "CRYPTO_RANKINGS", "BEST_PERFORMING_COINS", "TOP_MARKET_CAPS" ], // eslint-disable-next-line validate: async (runtime, _message) => { await validateCoingeckoConfig(runtime); return true; }, // Comprehensive endpoint for market rankings, supports up to 250 coins per request description: "Get ranked list of top cryptocurrencies sorted by market metrics (without specifying coins)", handler: async (runtime, message, state, _options, callback) => { var _a, _b, _c, _d, _e, _f, _g, _h; logger2.log("Starting CoinGecko GET_MARKETS handler..."); let currentState = state; if (!currentState) { currentState = await runtime.composeState(message); } else { currentState = await runtime.composeState(message); } try { const config = await validateCoingeckoConfig(runtime); const { baseUrl, apiKey, headerKey } = getApiConfig(config); const categories = await getCategoriesData(runtime); const marketsContext = getMarketsTemplate.replace( "{{categories}}", categories.map((c) => `- ${c.name} (ID: ${c.category_id})`).join("\n") ).replace( "{{recentMessages}}", currentState.recentMessagesFormatted || "" ); const result = await runtime.useModel(ModelType.TEXT_SMALL, { prompt: marketsContext, schema: GetMarketsSchema }); if (!isGetMarketsContent(result)) { logger2.error("Invalid market data format received"); return false; } const content = result; logger2.log("Content from template:", content); if (!content) { return false; } const formattedCategory = formatCategory(content.category, categories); if (content.category && !formattedCategory) { throw new Error( `Invalid category: ${content.category}. Please choose from the available categories.` ); } logger2.log("Making API request with params:", { url: `${baseUrl}/coins/markets`, category: formattedCategory, vs_currency: content.vs_currency, order: content.order, per_page: content.per_page, page: content.page }); const response = await axios2.get( `${baseUrl}/coins/markets`, { headers: { accept: "application/json", [headerKey]: apiKey }, params: { vs_currency: content.vs_currency, category: formattedCategory, order: content.order, per_page: content.per_page, page: content.page, sparkline: content.sparkline } } ); if (!((_a = response.data) == null ? void 0 : _a.length)) { throw new Error("No market data received from CoinGecko API"); } const formattedData = response.data.map((coin) => ({ name: coin.name, symbol: coin.symbol.toUpperCase(), marketCapRank: coin.market_cap_rank, currentPrice: coin.current_price, priceChange24h: coin.price_change_24h, priceChangePercentage24h: coin.price_change_percentage_24h, marketCap: coin.market_cap, volume24h: coin.total_volume, high24h: coin.high_24h, low24h: coin.low_24h, circulatingSupply: coin.circulating_supply, totalSupply: coin.total_supply, maxSupply: coin.max_supply, lastUpdated: coin.last_updated })); const categoryDisplay = content.category ? `${((_b = categories.find((c) => c.category_id === formattedCategory)) == null ? void 0 : _b.name.toUpperCase()) || content.category.toUpperCase()} ` : ""; const responseText = [ `Top ${formattedData.length} ${categoryDisplay}Cryptocurrencies by ${content.order === "volume_desc" || content.order === "volume_asc" ? "Volume" : "Market Cap"}:`, ...formattedData.map( (coin, index) => `${index + 1}. ${coin.name} (${coin.symbol}) | $${coin.currentPrice.toLocaleString()} | ${coin.priceChangePercentage24h.toFixed(2)}% | MCap: $${(coin.marketCap / 1e9).toFixed(2)}B` ) ].join("\n"); logger2.success("Market data retrieved successfully!"); if (callback) { callback({ text: responseText, content: { markets: formattedData, params: { vs_currency: content.vs_currency, category: content.category, order: content.order, per_page: content.per_page, page: content.page }, timestamp: (/* @__PURE__ */ new Date()).toISOString() } }); } return true; } catch (error) { logger2.error("Error in GET_MARKETS handler:", error); let errorMessage; if (((_c = error.response) == null ? void 0 : _c.status) === 429) { errorMessage = "Rate limit exceeded. Please try again later."; } else if (((_d = error.response) == null ? void 0 : _d.status) === 403) { errorMessage = "This endpoint requires a CoinGecko Pro API key. Please upgrade your plan to access this data."; } else if (((_e = error.response) == null ? void 0 : _e.status) === 400) { errorMessage = "Invalid request parameters. Please check your input."; } else { errorMessage = `Error fetching market data: ${error.message}`; } if (callback) { callback({ text: errorMessage, error: { message: error.message, statusCode: (_f = error.response) == null ? void 0 : _f.status, params: (_g = error.config) == null ? void 0 : _g.params, requiresProPlan: ((_h = error.response) == null ? void 0 : _h.status) === 403 } }); } return false; } }, examples: [ [ { name: "{{user1}}", content: { text: "Show me the top cryptocurrencies by market cap" } }, { name: "{{agent}}", content: { text: "I'll fetch the current market data for top cryptocurrencies.", action: "GET_MARKETS" } }, { name: "{{agent}}", content: { text: "Here are the top cryptocurrencies:\n1. Bitcoin (BTC) | $45,000 | +2.5% | MCap: $870.5B\n{{dynamic}}" } } ] ] }; // src/actions/getPrice.ts import { logger as logger4, ModelType as ModelType2 } from "@elizaos/core"; import axios4 from "axios"; import { z as z3 } from "zod"; // src/providers/coinsProvider.ts import { logger as logger3 } from "@elizaos/core"; import axios3 from "axios"; var CACHE_TTL2 = 5 * 60; var MAX_RETRIES2 = 3; async function fetchCoins(runtime, includePlatform = false) { var _a; const config = await validateCoingeckoConfig(runtime); const { baseUrl, apiKey, headerKey } = getApiConfig(config); const response = await axios3.get(`${baseUrl}/coins/list`, { params: { include_platform: includePlatform }, headers: { accept: "application/json", [headerKey]: apiKey }, timeout: 5e3 // 5 second timeout }); if (!((_a = response.data) == null ? void 0 : _a.length)) { throw new Error("Invalid coins data received"); } return response.data; } async function fetchWithRetry2(runtime, includePlatform = false) { let lastError = null; for (let i = 0; i < MAX_RETRIES2; i++) { try { return await fetchCoins(runtime, includePlatform); } catch (error) { lastError = error; logger3.error(`Coins fetch attempt ${i + 1} failed:`, error); await new Promise((resolve) => setTimeout(resolve, 1e3 * (i + 1))); } } throw lastError || new Error("Failed to fetch coins after multiple attempts"); } var cache2 = {}; async function getCoins(runtime, includePlatform = false) { try { if (cache2.data && cache2.timestamp && Date.now() - cache2.timestamp < CACHE_TTL2 * 1e3) { return cache2.data; } const coins = await fetchWithRetry2(runtime, includePlatform); cache2.data = coins; cache2.timestamp = Date.now(); return coins; } catch (error) { logger3.error("Error fetching coins:", error); throw error; } } function formatCoinsContext(coins) { const popularCoins = [ "bitcoin", "ethereum", "binancecoin", "ripple", "cardano", "solana", "polkadot", "dogecoin" ]; const popular = coins.filter((c) => popularCoins.includes(c.id)).map((c) => `${c.name} (${c.symbol.toUpperCase()}) - ID: ${c.id}`); return ` Available cryptocurrencies: Popular coins: ${popular.map((c) => `- ${c}`).join("\n")} Total available coins: ${coins.length} You can use these coin IDs when querying specific cryptocurrency data. `.trim(); } var coinsProvider = { name: "COINS_PROVIDER", // eslint-disable-next-line get: async (runtime, message, state) => { try { const coins = await getCoins(runtime); return { text: formatCoinsContext(coins), data: { coins } }; } catch (error) { logger3.error("Coins provider error:", error); return { text: "Cryptocurrency list is temporarily unavailable. Please try again later." }; } } }; async function getCoinsData(runtime, includePlatform = false) { return getCoins(runtime, includePlatform); } // src/templates/price.ts var getPriceTemplate = ` Extract the following parameters for cryptocurrency price data: - **coinIds** (string | string[]): The ID(s) of the cryptocurrency/cryptocurrencies to get prices for (e.g., "bitcoin" or ["bitcoin", "ethereum"]) - **currency** (string | string[]): The currency/currencies to display prices in (e.g., "usd" or ["usd", "eur", "jpy"]) - defaults to ["usd"] - **include_market_cap** (boolean): Whether to include market cap data - defaults to false - **include_24hr_vol** (boolean): Whether to include 24h volume data - defaults to false - **include_24hr_change** (boolean): Whether to include 24h price change data - defaults to false - **include_last_updated_at** (boolean): Whether to include last update timestamp - defaults to false Provide the values in the following JSON format: \`\`\`json { "coinIds": "bitcoin", "currency": ["usd"], "include_market_cap": false, "include_24hr_vol": false, "include_24hr_change": false, "include_last_updated_at": false } \`\`\` Example request: "What's the current price of Bitcoin?" Example response: \`\`\`json { "coinIds": "bitcoin", "currency": ["usd"], "include_market_cap": false, "include_24hr_vol": false, "include_24hr_change": false, "include_last_updated_at": false } \`\`\` Example request: "Show me ETH price and market cap in EUR with last update time" Example response: \`\`\`json { "coinIds": "ethereum", "currency": ["eur"], "include_market_cap": true, "include_24hr_vol": false, "include_24hr_change": false, "include_last_updated_at": true } \`\`\` Example request: "What's the current price of Bitcoin in USD, JPY and EUR?" Example response: \`\`\`json { "coinIds": "bitcoin", "currency": ["usd", "jpy", "eur"], "include_market_cap": false, "include_24hr_vol": false, "include_24hr_change": false, "include_last_updated_at": false } \`\`\` Here are the recent user messages for context: {{recentMessages}} Based on the conversation above, if the request is for cryptocurrency price data, extract the appropriate parameters and respond with a JSON object. If the request is not related to price data, respond with null.`; // src/actions/getPrice.ts var GetPriceSchema = z3.object({ coinIds: z3.union([z3.string(), z3.array(z3.string())]), currency: z3.union([z3.string(), z3.array(z3.string())]).default(["usd"]), include_market_cap: z3.boolean().default(false), include_24hr_vol: z3.boolean().default(false), include_24hr_change: z3.boolean().default(false), include_last_updated_at: z3.boolean().default(false) }); var isGetPriceContent = (obj) => { return GetPriceSchema.safeParse(obj).success; }; function formatCoinIds(input) { if (Array.isArray(input)) { return input.join(","); } return input; } var getPrice_default = { name: "GET_PRICE", similes: [ "COIN_PRICE_CHECK", "SPECIFIC_COINS_PRICE", "COIN_PRICE_LOOKUP", "SELECTED_COINS_PRICE", "PRICE_DETAILS", "COIN_PRICE_DATA" ], // eslint-disable-next-line validate: async (runtime, _message) => { await validateCoingeckoConfig(runtime); return true; }, description: "Get price and basic market data for one or more specific cryptocurrencies (by name/symbol)", handler: async (runtime, message, state, _options, callback) => { var _a, _b, _c, _d, _e, _f; logger4.log("Starting CoinGecko GET_PRICE handler..."); let currentState = state; if (!currentState) { currentState = await runtime.composeState(message); } else { currentState = await runtime.composeState(message); } try { logger4.log("Composing price context..."); const priceContext = getPriceTemplate.replace( "{{recentMessages}}", currentState.recentMessagesFormatted || "" ); logger4.log("Generating content from template..."); const result = await runtime.useModel(ModelType2.TEXT_SMALL, { prompt: priceContext, schema: GetPriceSchema }); if (!isGetPriceContent(result)) { logger4.error("Invalid price request format"); return false; } const content = result; logger4.log("Generated content:", content); const currencies = Array.isArray(content.currency) ? content.currency : [content.currency]; const vs_currencies = currencies.join(",").toLowerCase(); const coinIds = formatCoinIds(content.coinIds); logger4.log("Formatted request parameters:", { coinIds, vs_currencies }); const config = await validateCoingeckoConfig(runtime); const { baseUrl, apiKey, headerKey } = getApiConfig(config); logger4.log(`Fetching prices for ${coinIds} in ${vs_currencies}...`); logger4.log("API request URL:", `${baseUrl}/simple/price`); logger4.log("API request params:", { ids: coinIds, vs_currencies, include_market_cap: content.include_market_cap, include_24hr_vol: content.include_24hr_vol, include_24hr_change: content.include_24hr_change, include_last_updated_at: content.include_last_updated_at }); const response = await axios4.get( `${baseUrl}/simple/price`, { params: { ids: coinIds, vs_currencies, include_market_cap: content.include_market_cap, include_24hr_vol: content.include_24hr_vol, include_24hr_change: content.include_24hr_change, include_last_updated_at: content.include_last_updated_at }, headers: { accept: "application/json", [headerKey]: apiKey } } ); if (Object.keys(response.data).length === 0) { throw new Error( "No price data available for the specified coins and currency" ); } const coins = await getCoinsData(runtime); const formattedResponse = Object.entries(response.data).map(([coinId, data]) => { const coin = coins.find((c) => c.id === coinId); const coinName = coin ? `${coin.name} (${coin.symbol.toUpperCase()})` : coinId; const parts = [`${coinName}:`]; for (const currency of currencies) { const upperCurrency = currency.toUpperCase(); if (data[currency]) { parts.push( ` ${upperCurrency}: ${data[currency].toLocaleString( void 0, { style: "currency", currency } )}` ); } if (content.include_market_cap) { const marketCap = data[`${currency}_market_cap`]; if (marketCap !== void 0) { parts.push( ` Market Cap (${upperCurrency}): ${marketCap.toLocaleString( void 0, { style: "currency", currency, maximumFractionDigits: 0 } )}` ); } } if (content.include_24hr_vol) { const volume = data[`${currency}_24h_vol`]; if (volume !== void 0) { parts.push( ` 24h Volume (${upperCurrency}): ${volume.toLocaleString( void 0, { style: "currency", currency, maximumFractionDigits: 0 } )}` ); } } if (content.include_24hr_change) { const change = data[`${currency}_24h_change`]; if (change !== void 0) { const changePrefix = change >= 0 ? "+" : ""; parts.push( ` 24h Change (${upperCurrency}): ${changePrefix}${change.toFixed(2)}%` ); } } } if (content.include_last_updated_at && data.last_updated_at) { const lastUpdated = new Date( data.last_updated_at * 1e3 ).toLocaleString(); parts.push(` Last Updated: ${lastUpdated}`); } return parts.join("\n"); }).filter(Boolean); if (formattedResponse.length === 0) { throw new Error("Failed to format price data for the specified coins"); } const responseText = formattedResponse.join("\n\n"); logger4.success("Price data retrieved successfully!"); if (callback) { callback({ text: responseText, content: { prices: Object.entries(response.data).reduce( (acc, [coinId, data]) => { const coinPrices = currencies.reduce( (currencyAcc, currency) => { const currencyData = { price: data[currency], marketCap: data[`${currency}_market_cap`], volume24h: data[`${currency}_24h_vol`], change24h: data[`${currency}_24h_change`], lastUpdated: data.last_updated_at }; Object.assign(currencyAcc, { [currency]: currencyData }); return currencyAcc; }, {} ); Object.assign(acc, { [coinId]: coinPrices }); return acc; }, {} ), params: { currencies: currencies.map((c) => c.toUpperCase()), include_market_cap: content.include_market_cap, include_24hr_vol: content.include_24hr_vol, include_24hr_change: content.include_24hr_change, include_last_updated_at: content.include_last_updated_at } } }); } return true; } catch (error) { logger4.error("Error in GET_PRICE handler:", error); let errorMessage; if (((_a = error.response) == null ? void 0 : _a.status) === 429) { errorMessage = "Rate limit exceeded. Please try again later."; } else if (((_b = error.response) == null ? void 0 : _b.status) === 403) { errorMessage = "This endpoint requires a CoinGecko Pro API key. Please upgrade your plan to access this data."; } else if (((_c = error.response) == null ? void 0 : _c.status) === 400) { errorMessage = "Invalid request parameters. Please check your input."; } else { errorMessage = error.message || "Failed to fetch price data"; } if (callback) { callback({ text: errorMessage, content: { error: error.message, statusCode: (_d = error.response) == null ? void 0 : _d.status, params: (_e = error.config) == null ? void 0 : _e.params, requiresProPlan: ((_f = error.response) == null ? void 0 : _f.status) === 403 } }); } return false; } }, examples: [ [ { name: "{{user1}}", content: { text: "What's the current price of Bitcoin?" } }, { name: "{{agent}}", content: { text: "I'll check the current Bitcoin price for you.", action: "GET_PRICE" } }, { name: "{{agent}}", content: { text: "The current price of Bitcoin is {{dynamic}} USD" } } ], [ { name: "{{user1}}", content: { text: "Check ETH and BTC prices in EUR with market cap" } }, { name: "{{agent}}", content: { text: "I'll check the current prices with market cap data.", action: "GET_PRICE" } }, { name: "{{agent}}", content: { text: "Bitcoin: EUR {{dynamic}} | Market Cap: \u20AC{{dynamic}}\nEthereum: EUR {{dynamic}} | Market Cap: \u20AC{{dynamic}}" } } ] ] }; // src/actions/getPricePerAddress.ts import { logger as logger5, ModelType as ModelType3 } from "@elizaos/core"; import axios5 from "axios"; import { z as z4 } from "zod"; // src/templates/priceAddress.ts var getPriceByAddressTemplate = ` Extract the following parameters for token price data: - **chainId** (string): The blockchain network ID (e.g., "ethereum", "polygon", "binance-smart-chain") - **tokenAddress** (string): The contract address of the token - **include_market_cap** (boolean): Whether to include market cap data - defaults to true Normalize chain IDs to lowercase names: ethereum, polygon, binance-smart-chain, avalanche, fantom, arbitrum, optimism, etc. Token address should be the complete address string, maintaining its original case. Provide the values in the following JSON format: \`\`\`json { "chainId": "ethereum", "tokenAddress": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "include_market_cap": true } \`\`\` Example request: "What's the price of USDC on Ethereum? Address: 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" Example response: \`\`\`json { "chainId": "ethereum", "tokenAddress": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "include_market_cap": true } \`\`\` Example request: "Check the price for this token on Polygon: 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174" Example response: \`\`\`json { "chainId": "polygon", "tokenAddress": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", "include_market_cap": true } \`\`\` Example request: "Get price for BONK token on Solana with address HeLp6NuQkmYB4pYWo2zYs22mESHXPQYzXbB8n4V98jwC" Example response: \`\`\`json { "chainId": "solana", "tokenAddress": "HeLp6NuQkmYB4pYWo2zYs22mESHXPQYzXbB8n4V98jwC" } \`\`\` Here are the recent user messages for context: {{recentMessages}} Based on the conversation above, use last question made and if the request is for token price data and includes both a chain and address, extract the appropriate parameters and respond with a JSON object. If the request is not related to token price data or missing required information, respond with null.`; // src/actions/getPricePerAddress.ts var GetTokenPriceSchema = z4.object({ chainId: z4.string(), tokenAddress: z4.string() }); var isGetTokenPriceContent = (obj) => { return GetTokenPriceSchema.safeParse(obj).success; }; var getPricePerAddress_default = { name: "GET_TOKEN_PRICE_BY_ADDRESS", similes: [ "FETCH_TOKEN_PRICE_BY_ADDRESS", "CHECK_TOKEN_PRICE_BY_ADDRESS", "LOOKUP_TOKEN_BY_ADDRESS" ], // eslint-disable-next-line validate: async (runtime, _message) => { await validateCoingeckoConfig(runtime); return true; }, description: "Get the current USD price for a token using its blockchain address", handler: async (runtime, message, state, _options, callback) => { var _a, _b, _c, _d, _e, _f, _g, _h, _i; logger5.log("Starting GET_TOKEN_PRICE_BY_ADDRESS handler..."); let currentState = state; if (!currentState) { currentState = await runtime.composeState(message); } else { currentState = await runtime.composeState(message); } try { logger5.log("Composing token price context..."); const context = getPriceByAddressTemplate.replace( "{{recentMessages}}", currentState.recentMessagesFormatted || "" ); logger5.log("Generating content from template..."); const result = await runtime.useModel(ModelType3.TEXT_SMALL, { prompt: context, schema: GetTokenPriceSchema }); if (!isGetTokenPriceContent(result)) { logger5.error("Invalid token price request format"); return false; } const content = result; logger5.log("Generated content:", content); const config = await validateCoingeckoConfig(runtime); const { baseUrl, apiKey, headerKey } = getApiConfig(config); logger5.log("Fetching token data..."); const response = await axios5.get( `${baseUrl}/coins/${content.chainId}/contract/${content.tokenAddress}`, { headers: { accept: "application/json", [headerKey]: apiKey } } ); const tokenData = response.data; if (!((_b = (_a = tokenData.market_data) == null ? void 0 : _a.current_price) == null ? void 0 : _b.usd)) { throw new Error( `No price data available for token ${content.tokenAddress} on ${content.chainId}` ); } const parts = [ `${tokenData.name} (${tokenData.symbol.toUpperCase()})`, `Address: ${content.tokenAddress}`, `Chain: ${content.chainId}`, `Price: $${tokenData.market_data.current_price.usd.toFixed(6)} USD` ]; if ((_c = tokenData.market_data.market_cap) == null ? void 0 : _c.usd) { parts.push( `Market Cap: $${tokenData.market_data.market_cap.usd.toLocaleString()} USD` ); } const responseText = parts.join("\n"); logger5.success("Token price data retrieved successfully!"); if (callback) { callback({ text: responseText, content: { token: { name: tokenData.name, symbol: tokenData.symbol, address: content.tokenAddress, chain: content.chainId, price: tokenData.market_data.current_price.usd, marketCap: (_d = tokenData.market_data.market_cap) == null ? void 0 : _d.usd } } }); } return true; } catch (error) { logger5.error("Error in GET_TOKEN_PRICE_BY_ADDRESS handler:", error); let errorMessage; if (((_e = error.response) == null ? void 0 : _e.status) === 429) { errorMessage = "Rate limit exceeded. Please try again later."; } else if (((_f = error.response) == null ? void 0 : _f.status) === 403) { errorMessage = "This endpoint requires a CoinGecko Pro API key. Please upgrade your plan to access this data."; } else if (((_g = error.response) == null ? void 0 : _g.status) === 400) { errorMessage = "Invalid request parameters. Please check your input."; } else { errorMessage = "Failed to fetch token price. Please try again later."; } if (callback) { callback({ text: errorMessage, content: { error: error.message, statusCode: (_h = error.response) == null ? void 0 : _h.status, requiresProPlan: ((_i = error.response) == null ? void 0 : _i.status) === 403 } }); } return false; } }, examples: [ [ { name: "{{user1}}", content: { text: "What's the price of the USDC token on Ethereum? The address is 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" } }, { name: "{{agent}}", content: { text: "I'll check the USDC token price for you.", action: "GET_TOKEN_PRICE_BY_ADDRESS" } }, { name: "{{agent}}", content: { text: "USD Coin (USDC)\nAddress: 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\nChain: ethereum\nPrice: {{dynamic}} USD\nMarket Cap: ${{dynamic}} USD" } } ] ] }; // src/actions/getTopGainersLosers.ts import { logger as logger6, ModelType as ModelType4 } from "@elizaos/core"; import axios6 from "axios"; import { z as z5 } from "zod"; // src/templates/gainersLosers.ts var getTopGainersLosersTemplate = ` Extract the following parameters for top gainers and losers data: - **vs_currency** (string): The target currency to display prices in (e.g., "usd", "eur") - defaults to "usd" - **duration** (string): Time range for price changes - one of "24h", "7d", "14d", "30d", "60d", "1y" - defaults to "24h" - **top_coins** (string): Filter by market cap ranking (e.g., "100", "1000") - defaults to "1000" Provide the values in the following JSON format: \`\`\`json { "vs_currency": "usd", "duration": "24h", "top_coins": "1000" } \`\`\` Example request: "Show me the biggest gainers and losers today" Example response: \`\`\`json { "vs_currency": "usd", "duration": "24h", "top_coins": "1000" } \`\`\` Example request: "What are the top movers in EUR for the past week?" Example response: \`\`\`json { "vs_currency": "eur", "duration": "7d", "top_coins": "300" } \`\`\` Example request: "Show me monthly performance of top 100 coins" Example response: \`\`\`json { "vs_currency": "usd", "duration": "30d", "top_coins": "100" } \`\`\` Here are the recent user messages for context: {{recentMessages}} Based on the conversation above, if the request is for top gainers and losers data, extract the appropriate parameters and respond with a JSON object. If the request is not related to top movers data, respond with null.`; // src/actions/getTopGainersLosers.ts var DurationEnum = z5.enum(["1h", "24h", "7d", "14d", "30d", "60d", "1y"]); var GetTopGainersLosersSchema = z5.object({ vs_currency: z5.string().default("usd"), duration: DurationEnum.default("24h"), top_coins: z5.string().default("1000") }); var isGetTopGainersLosersContent = (obj) => { return GetTopGainersLosersSchema.safeParse(obj).success; }; var getTopGainersLosers_default = { name: "GET_TOP_GAINERS_LOSERS", similes: [ "TOP_MOVERS", "BIGGEST_GAINERS", "BIGGEST_LOSERS", "PRICE_CHANGES", "BEST_WORST_PERFORMERS" ], // eslint-disable-next-line validate: async (runtime, _message) => { await validateCoingeckoConfig(runtime); return true; }, description: "Get list of top gaining and losing cryptocurrencies by price change", handler: async (runtime, message, state, _options, callback) => { var _a, _b, _c, _d, _e, _f; logger6.log("Starting CoinGecko GET_TOP_GAINERS_LOSERS handler..."); let currentState = state; if (!currentState) { currentState = await runtime.composeState(message); } else { currentState = await runtime.composeState(message); } try { logger6.log("Composing gainers/losers context..."); const context = getTopGainersLosersTemplate.replace( "{{recentMessages}}", currentState.recentMessagesFormatted || "" ); logger6.log("Generating content from template..."); const result = await runtime.useModel(ModelType4.TEXT_LARGE, { prompt: context, schema: GetTopGainersLosersSchema }); if (!isGetTopGainersLosersContent(result)) { logger6.error("Invalid gainers/losers request format"); return false; } const content = result; logger6.log("Generated content:", content); const config = await validateCoingeckoConfig(runtime); const { baseUrl, apiKey, headerKey } = getApiConfig(config); logger6.log("Fetching top gainers/losers data..."); logger6.log("API request params:", { vs_currency: content.vs_currency, duration: content.duration, top_coins: content.top_coins }); const response = await axios6.get( `${baseUrl}/coins/top_gainers_losers`, { headers: { accept: "application/json", [headerKey]: apiKey }, params: { vs_currency: content.vs_currency, duration: content.duration, top_coins: content.top_coins } } ); if (!response.data) { throw new Error("No data received from CoinGecko API"); } const responseText = [ "Top Gainers:", ...response.data.top_gainers.map((coin, index) => { const changeKey = `usd_${content.duration}_change`; const change = coin[changeKey]; return `${index + 1}. ${coin.name} (${coin.symbol.toUpperCase()}) | $${coin.usd.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 8 })} | ${change >= 0 ? "+" : ""}${change.toFixed(2)}%${coin.market_cap_rank ? ` | Rank #${coin.market_cap_rank}` : ""}`; }), "", "Top Losers:", ...response.data.top_losers.map((coin, index) => { const changeKey = `usd_${content.duration}_change`; const change = coin[changeKey]; return `${index + 1}. ${coin.name} (${coin.symbol.toUpperCase()}) | $${coin.usd.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 8 })} | ${change >= 0 ? "+" : ""}${change.toFixed(2)}%${coin.market_cap_rank ? ` | Rank #${coin.market_cap_rank}` : ""}`; }) ].join("\n"); if (callback) { callback({ text: responseText, content: { data: response.data, params: { vs_currency: content.vs_currency, duration: content.duration, top_coins: content.top_coins } } }); } return true; } catch (error) { logger6.error("Error in GET_TOP_GAINERS_LOSERS handler:", error); let errorMessage; if (((_a = error.response) == null ? void 0 : _a.status) === 429) { errorMessage = "Rate limit exceeded. Please try again later."; } else if (((_b = error.response) == null ? void 0 : _b.status) === 403) { errorMessage = "This endpoint requires a CoinGecko Pro API key. Please upgrade your plan to access this data."; } else if (((_c = error.response) == null ? void 0 : _c.status) === 400) { errorMessage = "Invalid request parameters. Please check your input."; } else { errorMessage = `Error fetching top gainers/losers data: ${error.message}`; } if (callback) { callback({ text: errorMessage, content: { error: error.message, statusCode: (_d = error.response) == null ? void 0 : _d.status, params: (_e = error.config) == null ? void 0 : _e.params, requiresProPlan: ((_f = error.response) == null ? void 0 : _f.status) === 403 } }); } return false; } }, examples: [ [ { name: "{{user1}}", content: { text: "What are the top gaining and losing cryptocurrencies?" } }, { name: "{{agent}}", content: { text: "I'll check the top gainers and losers for you.", action: "GET_TOP_GAINERS_LOSERS" } }, { name: "{{agent}}", content: { text: "Here are the top gainers and losers:\nTop Gainers:\n1. Bitcoin (BTC) | $45,000 | +5.2% | Rank #1\n{{dynamic}}" } } ], [ { name: "{{user1}}", content: { text: "Show me the best and worst performing crypto today" } }, { name: "{{agent}}", content: { text: "I'll fetch the current top movers in the crypto market.", action: "GET_TOP_GAINERS_LOSERS" } }, { name: "{{agent}}", content: { text: "Here are today's best and worst performers:\n{{dynamic}}" } } ] ] }; // src/actions/getTrending.ts import { logger as logger7, ModelType as ModelType5 } from "@elizaos/core"; import axios7 from "axios"; import { z as z6 } from "zod"; // src/templates/trending.ts var getTrendingTemplate = ` Extract the following parameters for trending data: - **include_nfts** (boolean): Whether to include NFTs in the response (default: true) - **include_categories** (boolean): Whether to include categories in the response (default: true) Provide the values in the following JSON format: \`\`\`json { "include_nfts": true, "include_categories": true } \`\`\` Example request: "What's trending in crypto?" Example response: \`\`\`json { "include_nfts": true, "include_categories": true } \`\`\` Example request: "Show me trending coins only" Example response: \`\`\`json { "include_nfts": false, "include_categories": false } \`\`\` Here are the recent user messages for context: {{recentMessages}} Based on the conversation above, if the request is for trending market data, extract the appropriate parameters and respond with a JSON object. If the request is not related to trending data, respond with null.`; // src/actions/getTrending.ts var GetTrendingSchema = z6.object({ include_nfts: z6.boolean().default(true), include_categories: z6.boolean().default(true) }); var isGetTrendingContent = (obj) => { return GetTrendingSchema.safeParse(obj).success; }; var getTrending_default = { name: "GET_TRENDING", similes: [ "TRENDING_COINS", "TRENDING_CRYPTO", "HOT_COINS", "POPULAR_COINS", "TRENDING_SEARCH" ], // eslint-disable-next-line validate: async (runtime, _message) => { await validateCoingeckoConfig(runtime); return true; }, description: "Get list of trending cryptocurrencies, NFTs, and categories from CoinGecko", handler: async (runtime, message, state, _options, callback) => { var _a, _b; logger7.log("Starting CoinGecko GET_TRENDING handler..."); let currentState = state; if (!currentState) { currentState = await runtime.composeState(message); } else { currentState = await runtime.composeState(message); } try { logger7.log("Composing trending context..."); const trendingContext = getTrendingTemplate.replace( "{{recentMessages}}", currentState.recentMessagesFormatted || "" ); const result = await runtime.useModel(ModelType5.TEXT_LARGE, { prompt: trendingContext, schema: GetTrendingSchema }); if (!isGetTrendingContent(result)) { logger7.error("Invalid trending request format"); return false; } const config = await validateCoingeckoConfig(runtime); const { baseUrl, apiKey, headerKey } = getApiConfig(config); logger7.log("Fetching trending data..."); const response = await axios7.get( `${baseUrl}/search/trending`, { headers: { [headerKey]: apiKey } } ); if (!response.data) { throw new Error("No data received from CoinGecko API"); } const formattedData = { coins: response.data.coins.map(({ item }) => ({ name: item.name, symbol: item.symbol.toUpperCase(), marketCapRank: item.market_cap_rank, id: item.id, thumbnail: item.thumb, largeImage: item.large })), nfts: response.data.nfts.map((nft) => ({ name: nft.name, symbol: nft.symbol, id: nft.id, thumbnail: nft.thumb })), categories: response.data.categories.map((category) => ({ name: category.name, id: category.id })) }; const responseText = [ "Trending Coins:", ...formattedData.coins.map( (coin, index) => `${index + 1}. ${coin.name} (${coin.symbol})${coin.marketCapRank ? ` - Rank #${coin.marketCapRank}` : ""}` ), "", "Trending NFTs:", ...formattedData.nfts.length ? formattedData.nfts.map( (nft, index) => `${index + 1}. ${nft.name} (${nft.symbol})` ) : ["No trending NFTs available"], "", "Trending Categories:", ...formattedData.categories.length ? formattedData.categories.map( (category, index) => `${index + 1}. ${category.name}` ) : ["No trending categories available"] ].join("\n"); logger7.success("Trending data retrieved successfully!"); if (callback) { callback({ text: responseText, content: { trending: formattedData, timestamp: (/* @__PURE__ */ new Date()).toISOString() } }); } return true; } catch (error) { logger7.error("Error in GET_TRENDING handler:", error); const errorMessage = ((_a = error.response) == null ? void 0 : _a.status) === 429 ? "Rate limit exceeded. Please try again later." : `Error fetching trending data: ${error.message}`; if (callback) { callback({ text: errorMessage, content: { error: error.message, statusCode: (_b = error.response) == null ? void 0 : _b.status } }); } return false; } }, examples: [ [ { name: "{{user1}}", content: { text: "What are the trending cryptocurrencies?" } }, { name: "{{agent}}", content: { text: "I'll check the trending cryptocurrencies for you.", action: "GET_TRENDING" } }, { name: "{{agent}}", content: { text: "Here are the trending cryptocurrencies:\n1. Bitcoin (BTC) - Rank #1\n2. Ethereum (ETH) - Rank #2\n{{dynamic}}" } } ], [ { name: "{{user1}}", content: { text: "Show me what's hot in crypto right now" } }, { name: "{{agent}}", content: { text: "I'll fetch the current trending cryptocurrencies.", action: "GET_TRENDING" } }, { name: "{{agent}}", content: { text: "Here are the trending cryptocurrencies:\n{{dynamic}}" } } ] ] }; // src/actions/getTrendingPools.ts import { logger as logger8, ModelType as ModelType6 } from "@elizaos/core"; import axios8 from "axios"; import { z as z7 } from "zod"; // src/templates/trendingPools.ts var getTrendingPoolsTemplate = `Determine if this is a trending pools request. If it is one of the specified situations, perform the corresponding action: Situation 1: "Get all trending pools"