UNPKG

nestai-framework

Version:
424 lines (376 loc) 13.4 kB
typescriptCopyEditexport const agentDetails = { name: "YourAgentName", // Replace with your agent's name symbol: "agent", // Replace with your agent's symbol initialBuyAmount: 100, // Initial amount of tokens to buy description: "Your AI agent token description here", twitter: "https://twitter.com/yourprofile", // Optional: your Twitter link telegram: "https://t.me/yourgroup", // Optional: your Telegram group website: "https://yourwebsite.com", // Optional: your website link imagePath: "src/agent_logo/example.png", // Path to your agent's logo image }; import fetch from "node-fetch"; import { checkAgentExists } from "../firebase.js"; import { getAgentData } from "../firebase.js"; // API and API Key for Bitquery const BITQUERY_API_URL = "https://streaming.bitquery.io/eap"; const BITQUERY_API_KEY = "YOUR_API_KEY"; // Interface to define the structure of TokenSupplyUpdate interface TokenSupplyUpdate { Marketcap: string; // Market cap in USD Currency: { Symbol: string; // Token symbol (e.g., BTC, ETH) MintAddress: string; // Token's mint address }; } // Interface for BalanceUpdate interface BalanceUpdate { Account: { Address: string; // Account address of the holder }; Holding: string; // Amount of token held } // Interface for Trade Currency (optional fields) interface TradeCurrency { Name?: string; // Token name MintAddress?: string; // Token mint address Symbol?: string; // Token symbol } // Interface for DEXTrade, includes the Buy currency interface DEXTrade { Currency: TradeCurrency; // Trade currency (buy or sell) } // Generic interface for Bitquery API responses interface BitqueryResponse<T> { data?: { Solana?: T; }; } /** * Formats the market cap into human-readable format (K/M). * @param value The market cap value in USD (string). * @returns The formatted market cap (e.g., "1M", "500K"). */ function formatMarketcap(value: string): string { const num = parseFloat(value); if (num >= 1_000_000) { return `${(num / 1_000_000).toFixed(0)}M`; } else if (num >= 1_000) { return `${(num / 1_000).toFixed(0)}K`; } return num.toFixed(0); } /** * Fetches market cap data for a token using Bitquery. * @param agentName The name of the agent requesting data. * @param count The number of results to fetch (max 30). * @param term The search term (Mint Address) for the token. */ async function fetchMarketcap(agentName: string, count: number, term: string): Promise<void> { if (count > 30) count = 30; // Limit count to 30 const query = ` query MyQuery { Solana { TokenSupplyUpdates( where: {TokenSupplyUpdate: {Currency: {MintAddress: {includes: "${term}"}}}} orderBy: {descending: Block_Time, descendingByField: "TokenSupplyUpdate_Marketcap"} limitBy: {by: TokenSupplyUpdate_Currency_MintAddress, count: 1} limit: {count: ${count}} ) { TokenSupplyUpdate { Marketcap: PostBalanceInUSD Currency { Symbol MintAddress } } } } }`; try { const response = await fetch(BITQUERY_API_URL, { method: "POST", headers: { "Content-Type": "application/json", "X-API-KEY": BITQUERY_API_KEY, }, body: JSON.stringify({ query }), }); if (!response.ok) { throw new Error(`Failed to fetch market cap data: ${response.statusText}`); } const data = (await response.json()) as BitqueryResponse<{ TokenSupplyUpdates: { TokenSupplyUpdate: TokenSupplyUpdate }[] }>; const updates = data?.data?.Solana?.TokenSupplyUpdates || []; if (updates.length === 0) { console.log(`No results found for term: "${term}"`); return; } console.log(`Marketcap Data for term: "${term}":`); updates.forEach(({ TokenSupplyUpdate }) => { const marketcap = formatMarketcap(TokenSupplyUpdate.Marketcap); const symbol = TokenSupplyUpdate.Currency.Symbol; const mintAddress = TokenSupplyUpdate.Currency.MintAddress; console.log(`${symbol} | ${mintAddress} | Marketcap: ${marketcap}`); }); } catch (error) { console.error("Error fetching market cap data from Bitquery:", error); } } /** * Fetches top holders for a specific token using Bitquery. * @param agentName The name of the agent requesting data. * @param mintAddress The mint address of the token. */ async function fetchTopHolders(agentName: string, mintAddress: string): Promise<void> { const query = ` query MyQuery { Solana(dataset: realtime) { BalanceUpdates( limit: { count: 10 } orderBy: { descendingByField: "BalanceUpdate_Holding_maximum" } where: { BalanceUpdate: { Currency: { MintAddress: { is: "${mintAddress}" } } } Transaction: { Result: { Success: true } } } ) { BalanceUpdate { Account { Address } Holding: PostBalance(maximum: Block_Slot) } } } } `; try { const response = await fetch(BITQUERY_API_URL, { method: "POST", headers: { "Content-Type": "application/json", "X-API-KEY": BITQUERY_API_KEY, }, body: JSON.stringify({ query }), }); if (!response.ok) { throw new Error(`Failed to fetch top holders: ${response.statusText}`); } const data = (await response.json()) as BitqueryResponse<{ BalanceUpdates: { BalanceUpdate: BalanceUpdate }[] }>; const balanceUpdates = data?.data?.Solana?.BalanceUpdates || []; if (balanceUpdates.length === 0) { console.log(`No top holders found for MintAddress: ${mintAddress}`); return; } console.log(`Fetching top 10 holders for: ${mintAddress}`); balanceUpdates.forEach(({ BalanceUpdate }) => { const address = BalanceUpdate?.Account?.Address || "Unknown Address"; const holding = BalanceUpdate?.Holding ? parseFloat(BalanceUpdate.Holding).toFixed(6) : "0.000000"; console.log(`${address} | Holdings: ${holding}`); }); } catch (error) { console.error("Error fetching top holders from Bitquery:", error); } } /** * Fetches the first top buyers for a specific token using Bitquery. * @param agentName The name of the agent requesting data. * @param mintAddress The mint address of the token. * @param count The number of top buyers to fetch. */ async function fetchFirstTopBuyers(agentName: string, mintAddress: string, count: number): Promise<void> { const query = ` query MyQuery { Solana { DEXTrades( where: { Trade: { Buy: { Currency: { MintAddress: { is: "${mintAddress}" } } } } } limit: { count: ${count} } orderBy: { ascending: Block_Time } ) { Trade { Buy { Amount Account { Token { Owner } } } } } } }`; try { const response = await fetch(BITQUERY_API_URL, { method: "POST", headers: { "Content-Type": "application/json", "X-API-KEY": BITQUERY_API_KEY, }, body: JSON.stringify({ query }), }); if (!response.ok) { throw new Error(`Failed to fetch top buyers: ${response.statusText}`); } const data = (await response.json()) as BitqueryResponse<{ DEXTrades: { Trade: { Buy: { Amount: string; Account: { Token: { Owner: string } } } } }[] }>; const trades = data?.data?.Solana?.DEXTrades || []; if (trades.length === 0) { console.log(`No top buyers found for MintAddress: ${mintAddress}`); return; } console.log(`Top ${count} buyers for: ${mintAddress}`); trades.forEach(({ Trade }) => { const amount = Trade.Buy.Amount; const owner = Trade.Buy.Account.Token.Owner; console.log(`Amount: ${amount} | Owner: ${owner}`); }); } catch (error) { console.error("Error fetching top buyers from Bitquery:", error); } } /** * Fetches trending tokens for Solana using Bitquery. * @param agentName The name of the agent requesting data. */ async function fetchTrendingTokens(agentName: string): Promise<void> { const query = ` query MyQuery { Solana { DEXTradeByTokens( where: {Transaction: {Result: {Success: true}}, Trade: {Side: {Currency: {MintAddress: {is: "So11111111111111111111111111111111111111112"}}}}, Block: {Time: {since: "2024-08-15T04:19:00Z"}}} orderBy: {} limit: {count: 5} ) { Trade { Currency { Name MintAddress Symbol } start: PriceInUSD min5: PriceInUSD( minimum: Block_Time if: {Block: {Time: {after: "2024-08-15T05:14:00Z"}}} ) end: PriceInUSD(maximum: Block_Time) Side { Currency { Symbol Name MintAddress } } } } } } `; try { const response = await fetch(BITQUERY_API_URL, { method: "POST", headers: { "Content-Type": "application/json", "X-API-KEY": BITQUERY_API_KEY, }, body: JSON.stringify({ query }), }); if (!response.ok) { throw new Error(`Failed to fetch trending tokens: ${response.statusText}`); } const data = (await response.json()) as BitqueryResponse<{ DEXTradeByTokens: { Trade: DEXTrade }[] }>; const trades = data?.data?.Solana?.DEXTradeByTokens || []; if (trades.length === 0) { console.log("No trending tokens found."); return; } console.log("Top 5 Trending Tokens 24h:"); trades.forEach(({ Trade }) => { const name = Trade.Currency.Name || "Unknown Token"; const mintAddress = Trade.Currency.MintAddress || "Unknown Address"; console.log(`${mintAddress} | ${name}`); }); } catch (error) { console.error("Error fetching trending tokens from Bitquery:", error); } } /** * Interacts with the agent to answer questions based on the type of query. * @param agentName The name of the agent. * @param question The question asked to the agent. */ async function interactAgent(agentName: string, question: string): Promise<void> { const hasMarketcapKeywords = /Marketcap/i.test(question); const hasTrendingKeywords = /Trending/i.test(question); const hasTopHoldersKeywords = /Top.*holders/i.test(question); const hasTopBuyersKeywords = /First.*top.*buyers/i.test(question); // Check if the agent exists in the system const agentExists = await checkAgentExists(agentName); if (!agentExists) { console.error(`Cannot process request. Agent "${agentName}" is not registered.`); return; } const agentData = await getAgentData(agentName); // Retrieve agent data const personality = agentData?.personality || "neutral"; // Default to "neutral" if not set // Process market cap query if (hasMarketcapKeywords) { const countMatch = question.match(/count:\s*(\d+)/i); const termMatch = question.match(/term:\s*"([^"]+)"/i); const count = countMatch ? parseInt(countMatch[1], 10) : 10; const term = termMatch ? termMatch[1] : ""; if (term) { await fetchMarketcap(agentName, count, term); } else { console.log("Please provide a valid search term."); } } // Process top holders query else if (hasTopHoldersKeywords) { const mintAddressMatch = question.match(/[A-Za-z0-9]{32,44}/); if (mintAddressMatch) { const mintAddress = mintAddressMatch[0]; await fetchTopHolders(agentName, mintAddress); } else { console.log("Please provide a valid MintAddress in the question."); } } // Process top buyers query else if (hasTopBuyersKeywords) { const mintAddressMatch = question.match(/[A-Za-z0-9]{32,44}/); const countMatch = question.match(/First.*top\s*(\d+)/i); const count = countMatch ? parseInt(countMatch[1], 10) : 10; if (mintAddressMatch) { const mintAddress = mintAddressMatch[0]; await fetchFirstTopBuyers(agentName, mintAddress, count); } else { console.log("Please provide a valid MintAddress in the question."); } } // Process trending tokens query else if (hasTrendingKeywords) { await fetchTrendingTokens(agentName); } else { console.log(`Unsupported question: "${question}"`); } } // Command line arguments and entry point const args = process.argv.slice(2); if (args.length < 3 || args[1] !== "ask") { console.error('Usage: npm run interact {agent_name} ask "Your question"'); process.exit(1); } const agentName = args[0]; const question = args.slice(2).join(" "); // Call the function to interact with the agent interactAgent(agentName, question).catch((error) => { console.error("An error occurred:", error); });