diff --git a/packages/plugin-nft-collections/src/index.ts b/packages/plugin-nft-collections/src/index.ts index b6de994e37..5d7ba3144e 100644 --- a/packages/plugin-nft-collections/src/index.ts +++ b/packages/plugin-nft-collections/src/index.ts @@ -1,21 +1,13 @@ import { Plugin, Action, - Provider, IAgentRuntime, Memory, State, ServiceType, Evaluator, } from "@ai16z/eliza"; -import axios from "axios"; - -interface NFTCollection { - name: string; - totalSupply: number; - floorPrice: number; - volume24h: number; -} +import { nftCollectionProvider } from "./providers/nft-collections"; interface NFTKnowledge { mentionsCollection: boolean; @@ -96,25 +88,6 @@ function extractNFTDetails(text: string): { }; } -const fetchNFTCollections = async (): Promise => { - const API_KEY = process.env.RESERVOIR_API_KEY; - const response = await axios.get( - "https://api.reservoir.tools/collections/v6", - { - headers: { - accept: "application/json", - "x-api-key": API_KEY, - }, - } - ); - return response.data.collections.map((collection: any) => ({ - name: collection.name, - totalSupply: collection.totalSupply, - floorPrice: collection.floorAsk.price.amount.native, - volume24h: collection.volume["1day"], - })); -}; - const sweepFloorNFTAction: Action = { name: "SWEEP_FLOOR_NFT", similes: ["BUY_FLOOR_NFT", "PURCHASE_FLOOR_NFT"], @@ -192,13 +165,7 @@ const nftCollectionAction: Action = { }, handler: async (runtime: IAgentRuntime, message: Memory) => { try { - const collections = await fetchNFTCollections(); - const response = collections - .map( - (c) => - `${c.name}: Supply: ${c.totalSupply}, Floor: ${c.floorPrice.toFixed(2)} ETH, 24h Volume: ${c.volume24h.toFixed(2)} ETH` - ) - .join("\n"); + const response = await nftCollectionProvider.get(runtime, message); await runtime.sendMessage(message.roomId, response); return true; } catch (error) { @@ -229,23 +196,6 @@ const nftCollectionAction: Action = { ], }; -const nftCollectionProvider: Provider = { - get: async (runtime: IAgentRuntime, message: Memory, state?: State) => { - try { - const collections = await fetchNFTCollections(); - return `Current top NFT collections on Ethereum:\n${collections - .map( - (c) => - `${c.name}: Supply: ${c.totalSupply}, Floor: ${c.floorPrice.toFixed(2)} ETH, 24h Volume: ${c.volume24h.toFixed(2)} ETH` - ) - .join("\n")}`; - } catch (error) { - console.error("Error in NFT collection provider:", error); - return "Unable to fetch NFT collection data at the moment."; - } - }, -}; - const nftCollectionPlugin: Plugin = { name: "nft-collection-plugin", description: diff --git a/packages/plugin-nft-collections/src/providers/nft-collections.ts b/packages/plugin-nft-collections/src/providers/nft-collections.ts new file mode 100644 index 0000000000..4f71a33fcf --- /dev/null +++ b/packages/plugin-nft-collections/src/providers/nft-collections.ts @@ -0,0 +1,85 @@ +import { Provider, IAgentRuntime, Memory, State } from "@ai16z/eliza"; +import axios from "axios"; + +interface NFTCollection { + name: string; + totalSupply: number; + floorPrice: number; + volume24h: number; +} + +const CACHE_TTL = 3600000; // 1 hour +let cachedCollections: NFTCollection[] | null = null; +let lastFetchTime = 0; + +async function fetchWithRetry( + url: string, + options: any, + retries = 3 +): Promise { + try { + return await axios.get(url, options); + } catch (error) { + if (retries > 0) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + return fetchWithRetry(url, options, retries - 1); + } + throw error; + } +} + +function sanitizeCollection(collection: any): NFTCollection { + return { + name: String(collection.name).slice(0, 100), + totalSupply: Math.max(0, parseInt(collection.totalSupply) || 0), + floorPrice: Math.max( + 0, + parseFloat(collection.floorAsk?.price?.amount?.native) || 0 + ), + volume24h: Math.max(0, parseFloat(collection.volume?.["1day"]) || 0), + }; +} + +async function fetchCollectionsWithCache(): Promise { + const now = Date.now(); + if (!cachedCollections || now - lastFetchTime > CACHE_TTL) { + const response = await fetchWithRetry( + "https://api.reservoir.tools/collections/v6", + { + headers: { + accept: "application/json", + "x-api-key": process.env.RESERVOIR_API_KEY, + }, + } + ); + + cachedCollections = response.data.collections.map(sanitizeCollection); + lastFetchTime = now; + } + return cachedCollections; +} + +function processCollections(collections: NFTCollection[]): string { + return collections + .sort((a, b) => b.volume24h - a.volume24h) + .slice(0, 10) + .map( + (collection) => + `${collection.name}: Supply: ${collection.totalSupply}, Floor: ${collection.floorPrice.toFixed( + 2 + )} ETH, 24h Volume: ${collection.volume24h.toFixed(2)} ETH` + ) + .join("\n"); +} + +export const nftCollectionProvider: Provider = { + get: async (runtime: IAgentRuntime, message: Memory, state?: State) => { + try { + const collections = await fetchCollectionsWithCache(); + return `Current top NFT collections on Ethereum:\n${processCollections(collections)}`; + } catch (error) { + console.error("Error fetching NFT collections:", error); + return "Unable to fetch NFT collection data at the moment."; + } + }, +};