From 4d5eb8a91b15c9905a485ad412e7f9089b367c63 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Mon, 23 Dec 2024 16:15:52 -0800 Subject: [PATCH 01/33] feat: add birdeye plugin --- agent/src/index.ts | 10 +- packages/plugin-birdeye/.npmignore | 6 + packages/plugin-birdeye/README.md | 77 ++++ packages/plugin-birdeye/eslint.config.mjs | 3 + packages/plugin-birdeye/package.json | 31 ++ packages/plugin-birdeye/src/index.ts | 57 +++ .../src/providers/__tests__/utils.test.ts | 145 +++++++ .../defi/__tests__/price-provider.test.ts | 218 ++++++++++ .../src/providers/defi/index.ts | 12 + .../src/providers/defi/networks-provider.ts | 140 +++++++ .../defi/ohlcv-base-quote-provider.ts | 244 +++++++++++ .../src/providers/defi/ohlcv-pair-provider.ts | 210 ++++++++++ .../src/providers/defi/ohlcv-provider.ts | 256 ++++++++++++ .../providers/defi/pair-trades-provider.ts | 245 +++++++++++ .../defi/pair-trades-seek-provider.ts | 266 ++++++++++++ .../providers/defi/price-history-provider.ts | 230 +++++++++++ .../providers/defi/price-multiple-provider.ts | 200 +++++++++ .../src/providers/defi/price-provider.ts | 175 ++++++++ .../providers/defi/price-volume-provider.ts | 234 +++++++++++ .../providers/defi/token-trades-provider.ts | 236 +++++++++++ .../providers/defi/trades-seek-provider.ts | 210 ++++++++++ .../plugin-birdeye/src/providers/index.ts | 148 +++++++ .../src/providers/pair/index.ts | 1 + .../providers/pair/pair-overview-provider.ts | 286 +++++++++++++ .../src/providers/search/index.ts | 1 + .../search/token-market-data-provider.ts | 214 ++++++++++ .../__tests__/token-overview-provider.test.ts | 189 +++++++++ .../token/all-market-list-provider.ts | 114 ++++++ .../src/providers/token/index.ts | 13 + .../providers/token/new-listing-provider.ts | 113 ++++++ .../token/token-creation-provider.ts | 199 +++++++++ .../providers/token/token-holder-provider.ts | 220 ++++++++++ .../providers/token/token-list-provider.ts | 198 +++++++++ .../providers/token/token-market-provider.ts | 217 ++++++++++ .../token/token-metadata-provider.ts | 197 +++++++++ .../token/token-mint-burn-provider.ts | 203 ++++++++++ .../token/token-overview-provider.ts | 266 ++++++++++++ .../token/token-security-provider.ts | 238 +++++++++++ .../providers/token/token-trade-provider.ts | 327 +++++++++++++++ .../providers/token/top-traders-provider.ts | 104 +++++ .../token/trending-tokens-provider.ts | 270 +++++++++++++ .../trader/gainers-losers-provider.ts | 228 +++++++++++ .../src/providers/trader/index.ts | 2 + .../providers/trader/trades-seek-provider.ts | 247 ++++++++++++ .../plugin-birdeye/src/providers/utils.ts | 298 ++++++++++++++ .../src/providers/wallet/index.ts | 6 + .../wallet/portfolio-multichain-provider.ts | 159 ++++++++ .../wallet/supported-networks-provider.ts | 131 ++++++ .../wallet/token-balance-provider.ts | 135 +++++++ ...transaction-history-multichain-provider.ts | 174 ++++++++ .../wallet/transaction-history-provider.ts | 381 ++++++++++++++++++ .../wallet/wallet-portfolio-provider.ts | 335 +++++++++++++++ packages/plugin-birdeye/tsconfig.json | 10 + packages/plugin-birdeye/tsup.config.ts | 29 ++ 54 files changed, 8855 insertions(+), 3 deletions(-) create mode 100644 packages/plugin-birdeye/.npmignore create mode 100644 packages/plugin-birdeye/README.md create mode 100644 packages/plugin-birdeye/eslint.config.mjs create mode 100644 packages/plugin-birdeye/package.json create mode 100644 packages/plugin-birdeye/src/index.ts create mode 100644 packages/plugin-birdeye/src/providers/__tests__/utils.test.ts create mode 100644 packages/plugin-birdeye/src/providers/defi/__tests__/price-provider.test.ts create mode 100644 packages/plugin-birdeye/src/providers/defi/index.ts create mode 100644 packages/plugin-birdeye/src/providers/defi/networks-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/defi/ohlcv-base-quote-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/defi/ohlcv-pair-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/defi/ohlcv-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/defi/pair-trades-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/defi/pair-trades-seek-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/defi/price-history-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/defi/price-multiple-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/defi/price-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/defi/price-volume-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/defi/token-trades-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/defi/trades-seek-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/index.ts create mode 100644 packages/plugin-birdeye/src/providers/pair/index.ts create mode 100644 packages/plugin-birdeye/src/providers/pair/pair-overview-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/search/index.ts create mode 100644 packages/plugin-birdeye/src/providers/search/token-market-data-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/token/__tests__/token-overview-provider.test.ts create mode 100644 packages/plugin-birdeye/src/providers/token/all-market-list-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/token/index.ts create mode 100644 packages/plugin-birdeye/src/providers/token/new-listing-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/token/token-creation-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/token/token-holder-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/token/token-list-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/token/token-market-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/token/token-metadata-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/token/token-mint-burn-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/token/token-overview-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/token/token-security-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/token/token-trade-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/token/top-traders-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/token/trending-tokens-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/trader/gainers-losers-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/trader/index.ts create mode 100644 packages/plugin-birdeye/src/providers/trader/trades-seek-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/utils.ts create mode 100644 packages/plugin-birdeye/src/providers/wallet/index.ts create mode 100644 packages/plugin-birdeye/src/providers/wallet/portfolio-multichain-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/wallet/supported-networks-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/wallet/token-balance-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/wallet/transaction-history-multichain-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/wallet/transaction-history-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/wallet/wallet-portfolio-provider.ts create mode 100644 packages/plugin-birdeye/tsconfig.json create mode 100644 packages/plugin-birdeye/tsup.config.ts diff --git a/agent/src/index.ts b/agent/src/index.ts index 1e49bae84f..11419d5790 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -1,4 +1,5 @@ import { PostgresDatabaseAdapter } from "@elizaos/adapter-postgres"; +import { RedisClient } from "@elizaos/adapter-redis"; import { SqliteDatabaseAdapter } from "@elizaos/adapter-sqlite"; import { AutoClientInterface } from "@elizaos/client-auto"; import { DiscordClientInterface } from "@elizaos/client-discord"; @@ -10,6 +11,7 @@ import { TwitterClientInterface } from "@elizaos/client-twitter"; import { AgentRuntime, CacheManager, + CacheStore, Character, Clients, DbCacheAdapter, @@ -24,15 +26,14 @@ import { settings, stringToUuid, validateCharacterConfig, - CacheStore, } from "@elizaos/core"; -import { RedisClient } from "@elizaos/adapter-redis"; import { zgPlugin } from "@elizaos/plugin-0g"; import { bootstrapPlugin } from "@elizaos/plugin-bootstrap"; import createGoatPlugin from "@elizaos/plugin-goat"; // import { intifacePlugin } from "@elizaos/plugin-intiface"; import { DirectClient } from "@elizaos/client-direct"; import { aptosPlugin } from "@elizaos/plugin-aptos"; +import { birdeyePlugin } from "@elizaos/plugin-birdeye"; import { advancedTradePlugin, coinbaseCommercePlugin, @@ -43,7 +44,6 @@ import { } from "@elizaos/plugin-coinbase"; import { confluxPlugin } from "@elizaos/plugin-conflux"; import { evmPlugin } from "@elizaos/plugin-evm"; -import { storyPlugin } from "@elizaos/plugin-story"; import { flowPlugin } from "@elizaos/plugin-flow"; import { imageGenerationPlugin } from "@elizaos/plugin-image-generation"; import { multiversxPlugin } from "@elizaos/plugin-multiversx"; @@ -51,6 +51,7 @@ import { nearPlugin } from "@elizaos/plugin-near"; import { nftGenerationPlugin } from "@elizaos/plugin-nft-generation"; import { createNodePlugin } from "@elizaos/plugin-node"; import { solanaPlugin } from "@elizaos/plugin-solana"; +import { storyPlugin } from "@elizaos/plugin-story"; import { suiPlugin } from "@elizaos/plugin-sui"; import { TEEMode, teePlugin } from "@elizaos/plugin-tee"; import { tonPlugin } from "@elizaos/plugin-ton"; @@ -508,6 +509,9 @@ export async function createAgent( ? confluxPlugin : null, nodePlugin, + getSecret(character, "BIRDEYE_API_KEY") + ? birdeyePlugin + : null, getSecret(character, "SOLANA_PUBLIC_KEY") || (getSecret(character, "WALLET_PUBLIC_KEY") && !getSecret(character, "WALLET_PUBLIC_KEY")?.startsWith("0x")) diff --git a/packages/plugin-birdeye/.npmignore b/packages/plugin-birdeye/.npmignore new file mode 100644 index 0000000000..078562ecea --- /dev/null +++ b/packages/plugin-birdeye/.npmignore @@ -0,0 +1,6 @@ +* + +!dist/** +!package.json +!readme.md +!tsup.config.ts \ No newline at end of file diff --git a/packages/plugin-birdeye/README.md b/packages/plugin-birdeye/README.md new file mode 100644 index 0000000000..3da190d956 --- /dev/null +++ b/packages/plugin-birdeye/README.md @@ -0,0 +1,77 @@ +# Eliza Birdeye Plugin + +A powerful plugin for Eliza that integrates with Birdeye's comprehensive DeFi and token analytics API. This plugin provides real-time access to blockchain data, token metrics, and DeFi analytics across multiple networks. + +## Features + +- **DeFi Analytics** + + - Real-time price and trading data + - Historical price tracking + - OHLCV (Open, High, Low, Close, Volume) data + - Trade analysis for tokens and pairs + +- **Token Intelligence** + + - Comprehensive token metadata + - Security information + - Token holder analytics + - Mint and burn tracking + - Market trends and new listings + +- **Wallet Analysis** + + - Multi-chain portfolio tracking + - Token balance monitoring + - Transaction history analysis + - Cross-chain analytics + +- **Market Research** + - Gainers and losers tracking + - Trending tokens + - Top trader analysis + - Market pair analytics + +## Installation + +```bash +npm install @eliza/plugin-birdeye +``` + +## Configuration + +Add the following to your Eliza configuration: + +```typescript +import { BirdeyePlugin } from "@eliza/plugin-birdeye"; + +export default { + plugins: [ + new BirdeyePlugin({ + apiKey: "YOUR_BIRDEYE_API_KEY", + }), + ], +}; +``` + +## Environment Variables + +``` +BIRDEYE_API_KEY=your_api_key_here +``` + +## Usage + +Once configured, the plugin provides access to Birdeye data through Eliza's interface. + +## API Reference + +The plugin provides access to all Birdeye API endpoints through structured interfaces. For detailed API documentation, visit [Birdeye's API Documentation](https://public-api.birdeye.so). + +## License + +MIT + +## Contributing + +Contributions are welcome! Please read our contributing guidelines before submitting pull requests. diff --git a/packages/plugin-birdeye/eslint.config.mjs b/packages/plugin-birdeye/eslint.config.mjs new file mode 100644 index 0000000000..92fe5bbebe --- /dev/null +++ b/packages/plugin-birdeye/eslint.config.mjs @@ -0,0 +1,3 @@ +import eslintGlobalConfig from "../../eslint.config.mjs"; + +export default [...eslintGlobalConfig]; diff --git a/packages/plugin-birdeye/package.json b/packages/plugin-birdeye/package.json new file mode 100644 index 0000000000..4e4edb3fb9 --- /dev/null +++ b/packages/plugin-birdeye/package.json @@ -0,0 +1,31 @@ +{ + "name": "@elizaos/plugin-birdeye", + "version": "0.1.7-alpha.1", + "main": "dist/index.js", + "type": "module", + "types": "dist/index.d.ts", + "dependencies": { + "@elizaos/core": "workspace:*", + "@coral-xyz/anchor": "0.30.1", + "@solana/spl-token": "0.4.9", + "@solana/web3.js": "1.95.8", + "bignumber": "1.1.0", + "bignumber.js": "9.1.2", + "bs58": "6.0.0", + "fomo-sdk-solana": "1.3.2", + "node-cache": "5.1.2", + "pumpdotfun-sdk": "1.3.2", + "tsup": "8.3.5", + "vitest": "2.1.4" + }, + "scripts": { + "build": "tsup --format esm --dts", + "dev": "tsup --format esm --dts --watch", + "lint": "eslint --fix --cache .", + "test": "vitest run" + }, + "peerDependencies": { + "form-data": "4.0.1", + "whatwg-url": "7.1.0" + } +} \ No newline at end of file diff --git a/packages/plugin-birdeye/src/index.ts b/packages/plugin-birdeye/src/index.ts new file mode 100644 index 0000000000..c73b2d36dd --- /dev/null +++ b/packages/plugin-birdeye/src/index.ts @@ -0,0 +1,57 @@ +import { Plugin } from "@elizaos/core"; +import { + gainersLosersProvider, + ohlcvProvider, + pairOverviewProvider, + priceMultipleProvider, + priceProvider, + priceVolumeProvider, + tokenCreationProvider, + tokenListProvider, + tokenMarketDataProvider, + tokenOverviewProvider, + tokenSecurityProvider, + tokenTradeProvider, + tradesSeekProvider, + transactionHistoryProvider, + trendingTokensProvider, + walletPortfolioProvider, +} from "./providers"; + +export const birdeyePlugin: Plugin = { + name: "birdeye", + description: "Birdeye Plugin for token data and analytics", + actions: [], + evaluators: [], + providers: [ + // DeFi providers + priceProvider, + priceMultipleProvider, + ohlcvProvider, + priceVolumeProvider, + + // Pair providers + pairOverviewProvider, + + // Search providers + tokenMarketDataProvider, + + // Token providers + tokenOverviewProvider, + tokenSecurityProvider, + tokenListProvider, + trendingTokensProvider, + tokenCreationProvider, + tokenTradeProvider, + + // Trader providers + gainersLosersProvider, + tradesSeekProvider, + + // Wallet providers + transactionHistoryProvider, + walletPortfolioProvider, + ], +}; + +export default birdeyePlugin; diff --git a/packages/plugin-birdeye/src/providers/__tests__/utils.test.ts b/packages/plugin-birdeye/src/providers/__tests__/utils.test.ts new file mode 100644 index 0000000000..cdc8922733 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/__tests__/utils.test.ts @@ -0,0 +1,145 @@ +import { + extractChain, + extractContractAddresses, + extractLimit, + extractTimeframe, + extractTimeRange, + formatPercentChange, + formatPrice, + formatTimestamp, + formatValue, + shortenAddress, + TIME_UNITS, +} from "../utils"; + +describe("Chain Extraction", () => { + test("extracts chain from text correctly", () => { + expect(extractChain("Check price on Solana")).toBe("solana"); + expect(extractChain("Look up Ethereum token")).toBe("ethereum"); + expect(extractChain("No chain mentioned")).toBe("solana"); // default + }); +}); + +describe("Contract Address Extraction", () => { + test("extracts Ethereum addresses correctly", () => { + const text = + "Token address is 0x1234567890123456789012345678901234567890"; + expect(extractContractAddresses(text)).toEqual([ + "0x1234567890123456789012345678901234567890", + ]); + }); + + test("extracts Solana addresses correctly", () => { + const text = + "Token address is TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; + expect(extractContractAddresses(text)).toEqual([ + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + ]); + }); + + test("extracts multiple addresses correctly", () => { + const text = + "0x1234567890123456789012345678901234567890 TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; + expect(extractContractAddresses(text)).toHaveLength(2); + }); +}); + +describe("Timeframe Extraction", () => { + test("extracts explicit timeframes correctly", () => { + expect(extractTimeframe("Show 1h chart")).toBe("1h"); + expect(extractTimeframe("Display 15m data")).toBe("15m"); + expect(extractTimeframe("Get 1d overview")).toBe("1d"); + }); + + test("extracts semantic timeframes correctly", () => { + expect(extractTimeframe("Show short term analysis")).toBe("15m"); + expect(extractTimeframe("Get medium term view")).toBe("1h"); + expect(extractTimeframe("Display long term data")).toBe("1d"); + }); + + test("returns default timeframe for unclear input", () => { + expect(extractTimeframe("Show me the data")).toBe("1h"); + }); +}); + +describe("Time Range Extraction", () => { + beforeEach(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date("2024-01-01T00:00:00Z")); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + test("extracts specific date ranges", () => { + const result = extractTimeRange("from 2023-12-01 to 2023-12-31"); + expect(result.start).toBe(new Date("2023-12-01").getTime() / 1000); + expect(result.end).toBe(new Date("2023-12-31").getTime() / 1000); + }); + + test("extracts relative time ranges", () => { + const now = Math.floor(Date.now() / 1000); + const result = extractTimeRange("24 hours ago"); + expect(result.end).toBe(now); + expect(result.start).toBe(now - TIME_UNITS.day); + }); + + test("handles semantic time ranges", () => { + const now = Math.floor(Date.now() / 1000); + const result = extractTimeRange("show me today's data"); + expect(result.end).toBe(now); + expect(result.start).toBe(now - TIME_UNITS.day); + }); +}); + +describe("Limit Extraction", () => { + test("extracts explicit limits", () => { + expect(extractLimit("show 20 results")).toBe(20); + expect(extractLimit("display 5 items")).toBe(5); + expect(extractLimit("fetch 200 records")).toBe(100); // clamped to max + }); + + test("extracts semantic limits", () => { + expect(extractLimit("show me everything")).toBe(100); + expect(extractLimit("give me a brief overview")).toBe(5); + expect(extractLimit("provide detailed analysis")).toBe(50); + }); + + test("returns default limit for unclear input", () => { + expect(extractLimit("show me the data")).toBe(10); + }); +}); + +describe("Formatting Functions", () => { + test("formats values correctly", () => { + expect(formatValue(1500000000)).toBe("$1.50B"); + expect(formatValue(1500000)).toBe("$1.50M"); + expect(formatValue(1500)).toBe("$1.50K"); + expect(formatValue(150)).toBe("$150.00"); + }); + + test("formats percent changes correctly", () => { + expect(formatPercentChange(10.5)).toBe("šŸ“ˆ 10.50%"); + expect(formatPercentChange(-5.25)).toBe("šŸ“‰ 5.25%"); + expect(formatPercentChange(undefined)).toBe("N/A"); + }); + + test("shortens addresses correctly", () => { + expect( + shortenAddress("0x1234567890123456789012345678901234567890") + ).toBe("0x1234...7890"); + expect(shortenAddress("short")).toBe("short"); + expect(shortenAddress("")).toBe("Unknown"); + }); + + test("formats timestamps correctly", () => { + const timestamp = 1704067200; // 2024-01-01 00:00:00 UTC + expect(formatTimestamp(timestamp)).toMatch(/2024/); + }); + + test("formats prices correctly", () => { + expect(formatPrice(123.456)).toBe("123.46"); + expect(formatPrice(0.000123)).toBe("1.23e-4"); + }); +}); diff --git a/packages/plugin-birdeye/src/providers/defi/__tests__/price-provider.test.ts b/packages/plugin-birdeye/src/providers/defi/__tests__/price-provider.test.ts new file mode 100644 index 0000000000..5c364f5848 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/defi/__tests__/price-provider.test.ts @@ -0,0 +1,218 @@ +import { IAgentRuntime, Memory, State } from "@elizaos/core"; +import { priceProvider } from "../price-provider"; + +// Mock data +const mockPriceData = { + price: 1.23, + timestamp: 1704067200, // 2024-01-01 00:00:00 UTC + token: "TEST", + priceChange24h: 0.05, + priceChange24hPercent: 4.23, +}; + +// Mock fetch globally +global.fetch = jest.fn(); + +describe("Price Provider", () => { + let mockRuntime: IAgentRuntime; + let mockMessage: Memory; + let mockState: State; + + beforeEach(() => { + // Reset mocks + jest.clearAllMocks(); + + // Mock runtime + mockRuntime = { + getSetting: jest.fn().mockReturnValue("mock-api-key"), + } as unknown as IAgentRuntime; + + // Mock message + mockMessage = { + content: { + text: "What is the price of 0x1234567890123456789012345678901234567890 on ethereum", + }, + } as Memory; + + // Mock state + mockState = {} as State; + + // Mock successful fetch response + (global.fetch as jest.Mock).mockResolvedValue({ + ok: true, + json: async () => ({ data: mockPriceData }), + }); + }); + + test("returns null when API key is missing", async () => { + (mockRuntime.getSetting as jest.Mock).mockReturnValue(null); + const result = await priceProvider.get( + mockRuntime, + mockMessage, + mockState + ); + expect(result).toBeNull(); + }); + + test("returns null when message does not contain price keywords", async () => { + mockMessage.content.text = "random message without price keywords"; + const result = await priceProvider.get( + mockRuntime, + mockMessage, + mockState + ); + expect(result).toBeNull(); + }); + + test("returns null when no contract address is found", async () => { + mockMessage.content.text = "what is the price of invalid-address"; + const result = await priceProvider.get( + mockRuntime, + mockMessage, + mockState + ); + expect(result).toBeNull(); + }); + + test("handles API error gracefully", async () => { + (global.fetch as jest.Mock).mockRejectedValue(new Error("API Error")); + const result = await priceProvider.get( + mockRuntime, + mockMessage, + mockState + ); + expect(result).toBeNull(); + }); + + test("handles 404 response gracefully", async () => { + (global.fetch as jest.Mock).mockResolvedValue({ + ok: false, + status: 404, + }); + const result = await priceProvider.get( + mockRuntime, + mockMessage, + mockState + ); + expect(result).toBeNull(); + }); + + test("formats price response correctly with all data", async () => { + const result = await priceProvider.get( + mockRuntime, + mockMessage, + mockState + ); + + expect(result).toContain( + `Price for ${mockPriceData.token} on Ethereum` + ); + expect(result).toContain( + `Current Price: $${mockPriceData.price.toFixed(2)}` + ); + expect(result).toContain( + `24h Change: $${mockPriceData.priceChange24h.toFixed(2)}` + ); + expect(result).toContain( + `24h Change %: ${mockPriceData.priceChange24hPercent.toFixed(2)}%` + ); + expect(result).toContain("Last Updated:"); + }); + + test("formats price response correctly with minimal data", async () => { + const minimalPriceData = { + price: 0.000123, + timestamp: 1704067200, + token: "TEST", + }; + + (global.fetch as jest.Mock).mockResolvedValue({ + ok: true, + json: async () => ({ data: minimalPriceData }), + }); + + const result = await priceProvider.get( + mockRuntime, + mockMessage, + mockState + ); + + expect(result).toContain( + `Price for ${minimalPriceData.token} on Ethereum` + ); + expect(result).toContain("Current Price: $1.23e-4"); // Scientific notation for small numbers + expect(result).not.toContain("24h Change:"); + expect(result).not.toContain("24h Change %:"); + expect(result).toContain("Last Updated:"); + }); + + test("extracts chain correctly", async () => { + mockMessage.content.text = + "what is the price of 0x1234567890123456789012345678901234567890 on ethereum"; + await priceProvider.get(mockRuntime, mockMessage, mockState); + + expect(global.fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + "x-chain": "ethereum", + }), + }) + ); + }); + + test("defaults to solana chain when not specified", async () => { + mockMessage.content.text = + "what is the price of 0x1234567890123456789012345678901234567890"; + await priceProvider.get(mockRuntime, mockMessage, mockState); + + expect(global.fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + "x-chain": "solana", + }), + }) + ); + }); + + test("recognizes various price keywords", async () => { + const priceKeywords = [ + "price", + "cost", + "worth", + "value", + "rate", + "quote", + "how much", + ]; + + for (const keyword of priceKeywords) { + mockMessage.content.text = `${keyword} of 0x1234567890123456789012345678901234567890`; + const result = await priceProvider.get( + mockRuntime, + mockMessage, + mockState + ); + expect(result).not.toBeNull(); + } + }); + + test("handles different address formats", async () => { + // Test Ethereum address + mockMessage.content.text = + "price of 0x1234567890123456789012345678901234567890"; + let result = await priceProvider.get( + mockRuntime, + mockMessage, + mockState + ); + expect(result).not.toBeNull(); + + // Test Solana address + mockMessage.content.text = + "price of TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; + result = await priceProvider.get(mockRuntime, mockMessage, mockState); + expect(result).not.toBeNull(); + }); +}); diff --git a/packages/plugin-birdeye/src/providers/defi/index.ts b/packages/plugin-birdeye/src/providers/defi/index.ts new file mode 100644 index 0000000000..af593176d4 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/defi/index.ts @@ -0,0 +1,12 @@ +export * from "./networks-provider"; +export * from "./ohlcv-base-quote-provider"; +export * from "./ohlcv-pair-provider"; +export * from "./ohlcv-provider"; +export * from "./pair-trades-provider"; +export * from "./pair-trades-seek-provider"; +export * from "./price-history-provider"; +export * from "./price-multiple-provider"; +export * from "./price-provider"; +export * from "./price-volume-provider"; +export * from "./token-trades-provider"; +export * from "./trades-seek-provider"; diff --git a/packages/plugin-birdeye/src/providers/defi/networks-provider.ts b/packages/plugin-birdeye/src/providers/defi/networks-provider.ts new file mode 100644 index 0000000000..458cbb881b --- /dev/null +++ b/packages/plugin-birdeye/src/providers/defi/networks-provider.ts @@ -0,0 +1,140 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { BASE_URL, makeApiRequest } from "../utils"; + +// Types +interface NetworkInfo { + name: string; + chainId: string; + rpcUrl: string; + explorerUrl: string; + status: "active" | "maintenance" | "deprecated"; + features: string[]; +} + +interface NetworksResponse { + networks: NetworkInfo[]; +} + +// Constants +const NETWORK_KEYWORDS = [ + "supported networks", + "available networks", + "supported chains", + "available chains", + "which networks", + "which chains", + "list networks", + "list chains", + "show networks", + "show chains", + "network support", + "chain support", +] as const; + +// Helper functions +const containsNetworkKeyword = (text: string): boolean => { + return NETWORK_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getNetworks = async ( + apiKey: string +): Promise => { + try { + const url = `${BASE_URL}/defi/networks`; + + elizaLogger.info("Fetching supported networks from:", url); + + return await makeApiRequest(url, { + apiKey, + chain: "solana", + }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching networks:", error.message); + } + return null; + } +}; + +const formatNetworkResponse = (data: NetworksResponse): string => { + let response = "Supported Networks on Birdeye\n\n"; + + // Group networks by status + const activeNetworks = data.networks.filter((n) => n.status === "active"); + const maintenanceNetworks = data.networks.filter( + (n) => n.status === "maintenance" + ); + const deprecatedNetworks = data.networks.filter( + (n) => n.status === "deprecated" + ); + + // Format active networks + if (activeNetworks.length > 0) { + response += "šŸŸ¢ Active Networks\n"; + activeNetworks.forEach((network) => { + response += `ā€¢ ${network.name}\n`; + response += ` - Chain ID: ${network.chainId}\n`; + response += ` - Features: ${network.features.join(", ")}\n`; + response += ` - Explorer: ${network.explorerUrl}\n\n`; + }); + } + + // Format maintenance networks + if (maintenanceNetworks.length > 0) { + response += "šŸŸ” Networks Under Maintenance\n"; + maintenanceNetworks.forEach((network) => { + response += `ā€¢ ${network.name}\n`; + response += ` - Chain ID: ${network.chainId}\n`; + response += ` - Features: ${network.features.join(", ")}\n\n`; + }); + } + + // Format deprecated networks + if (deprecatedNetworks.length > 0) { + response += "šŸ”“ Deprecated Networks\n"; + deprecatedNetworks.forEach((network) => { + response += `ā€¢ ${network.name}\n`; + response += ` - Chain ID: ${network.chainId}\n\n`; + }); + } + + return response.trim(); +}; + +export const networksProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsNetworkKeyword(messageText)) { + return null; + } + + elizaLogger.info("NETWORKS provider activated"); + + const networksData = await getNetworks(apiKey); + + if (!networksData) { + return null; + } + + return formatNetworkResponse(networksData); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/defi/ohlcv-base-quote-provider.ts b/packages/plugin-birdeye/src/providers/defi/ohlcv-base-quote-provider.ts new file mode 100644 index 0000000000..0a07937ade --- /dev/null +++ b/packages/plugin-birdeye/src/providers/defi/ohlcv-base-quote-provider.ts @@ -0,0 +1,244 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + Timeframe, + extractChain, + extractContractAddresses, + extractLimit, + extractTimeRange, + extractTimeframe, + formatTimestamp, + formatValue, + makeApiRequest, +} from "../utils"; + +// Types +interface OHLCVData { + timestamp: number; + open: number; + high: number; + low: number; + close: number; + volume: number; +} + +interface BaseQuoteOHLCVResponse { + data: OHLCVData[]; + pair: { + baseToken: string; + quoteToken: string; + }; +} + +// Constants +const BASE_QUOTE_OHLCV_KEYWORDS = [ + "base quote ohlcv", + "base quote candlestick", + "base quote candles", + "base quote chart", + "base quote price history", + "base quote historical data", + "base quote market data", + "base quote trading data", + "base/quote chart", + "base/quote price", + "base/quote history", + "base/quote movement", + "token pair chart", + "token pair price", + "token pair history", + "token pair movement", +] as const; + +// Helper functions +const containsBaseQuoteOHLCVKeyword = (text: string): boolean => { + return BASE_QUOTE_OHLCV_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getBaseQuoteOHLCV = async ( + apiKey: string, + baseAddress: string, + quoteAddress: string, + timeframe: Timeframe, + chain: Chain, + limit: number +): Promise => { + try { + const params = new URLSearchParams({ + base_address: baseAddress, + quote_address: quoteAddress, + timeframe, + limit: limit.toString(), + }); + const url = `${BASE_URL}/defi/ohlcv_base_quote?${params.toString()}`; + + elizaLogger.info( + `Fetching base/quote OHLCV data for ${baseAddress}/${quoteAddress} with ${timeframe} timeframe on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { + apiKey, + chain, + }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error( + "Error fetching base/quote OHLCV data:", + error.message + ); + } + return null; + } +}; + +const formatOHLCVData = (data: OHLCVData): string => { + const timestamp = formatTimestamp(data.timestamp); + const change = ((data.close - data.open) / data.open) * 100; + const trend = change >= 0 ? "šŸŸ¢" : "šŸ”“"; + + let response = `${trend} ${timestamp}\n`; + response += `ā€¢ Open: ${formatValue(data.open)}\n`; + response += `ā€¢ High: ${formatValue(data.high)}\n`; + response += `ā€¢ Low: ${formatValue(data.low)}\n`; + response += `ā€¢ Close: ${formatValue(data.close)}\n`; + response += `ā€¢ Volume: ${formatValue(data.volume)}\n`; + response += `ā€¢ Change: ${change >= 0 ? "+" : ""}${change.toFixed(2)}%`; + + return response; +}; + +const formatBaseQuoteOHLCVResponse = ( + data: BaseQuoteOHLCVResponse, + timeframe: Timeframe, + chain: Chain, + timeRange: { start: number; end: number } +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + const startDate = formatTimestamp(timeRange.start); + const endDate = formatTimestamp(timeRange.end); + + let response = `Base/Quote OHLCV Data for ${data.pair.baseToken}/${data.pair.quoteToken} on ${chainName}\n`; + response += `Timeframe: ${timeframe} (${startDate} to ${endDate})\n\n`; + + if (data.data.length === 0) { + return response + "No OHLCV data found for this base/quote pair."; + } + + // Calculate summary statistics + const latestPrice = data.data[data.data.length - 1].close; + const earliestPrice = data.data[0].open; + const priceChange = ((latestPrice - earliestPrice) / earliestPrice) * 100; + const totalVolume = data.data.reduce((sum, d) => sum + d.volume, 0); + const highestPrice = Math.max(...data.data.map((d) => d.high)); + const lowestPrice = Math.min(...data.data.map((d) => d.low)); + const averageVolume = totalVolume / data.data.length; + const volatility = ((highestPrice - lowestPrice) / lowestPrice) * 100; + + response += `šŸ“Š Summary\n`; + response += `ā€¢ Current Price: ${formatValue(latestPrice)}\n`; + response += `ā€¢ Period Change: ${priceChange >= 0 ? "+" : ""}${priceChange.toFixed(2)}%\n`; + response += `ā€¢ Total Volume: ${formatValue(totalVolume)}\n`; + response += `ā€¢ Average Volume: ${formatValue(averageVolume)}\n`; + response += `ā€¢ Highest Price: ${formatValue(highestPrice)}\n`; + response += `ā€¢ Lowest Price: ${formatValue(lowestPrice)}\n`; + response += `ā€¢ Volatility: ${volatility.toFixed(2)}%\n\n`; + + // Add trend analysis + const trendStrength = Math.abs(priceChange); + let trendAnalysis = ""; + if (trendStrength < 1) { + trendAnalysis = "Sideways movement with low volatility"; + } else if (trendStrength < 5) { + trendAnalysis = + priceChange > 0 ? "Slight upward trend" : "Slight downward trend"; + } else if (trendStrength < 10) { + trendAnalysis = + priceChange > 0 + ? "Moderate upward trend" + : "Moderate downward trend"; + } else { + trendAnalysis = + priceChange > 0 ? "Strong upward trend" : "Strong downward trend"; + } + + response += `šŸ“ˆ Trend Analysis\n`; + response += `ā€¢ ${trendAnalysis}\n`; + response += `ā€¢ Volatility is ${volatility < 5 ? "low" : volatility < 15 ? "moderate" : "high"}\n\n`; + + response += `šŸ“Š Recent Data\n`; + // Show only the last 5 entries + const recentData = data.data.slice(-5); + recentData.forEach((candle, index) => { + response += `${index + 1}. ${formatOHLCVData(candle)}\n\n`; + }); + + if (data.data.length > 5) { + response += `Showing last 5 of ${data.data.length} candles.`; + } + + return response; +}; + +export const baseQuoteOHLCVProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsBaseQuoteOHLCVKeyword(messageText)) { + return null; + } + + const addresses = extractContractAddresses(messageText); + if (addresses.length !== 2) { + return null; + } + + const chain = extractChain(messageText); + const timeframe = extractTimeframe(messageText); + const timeRange = extractTimeRange(messageText); + const limit = extractLimit(messageText); + + elizaLogger.info( + `BASE/QUOTE OHLCV provider activated for base ${addresses[0]} and quote ${addresses[1]} with ${timeframe} timeframe on ${chain}` + ); + + const ohlcvData = await getBaseQuoteOHLCV( + apiKey, + addresses[0], + addresses[1], + timeframe, + chain, + limit + ); + + if (!ohlcvData) { + return null; + } + + return formatBaseQuoteOHLCVResponse( + ohlcvData, + timeframe, + chain, + timeRange + ); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/defi/ohlcv-pair-provider.ts b/packages/plugin-birdeye/src/providers/defi/ohlcv-pair-provider.ts new file mode 100644 index 0000000000..8cfa6f9fac --- /dev/null +++ b/packages/plugin-birdeye/src/providers/defi/ohlcv-pair-provider.ts @@ -0,0 +1,210 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + Timeframe, + extractChain, + extractContractAddresses, + extractLimit, + extractTimeRange, + extractTimeframe, + formatTimestamp, + formatValue, + makeApiRequest, +} from "../utils"; + +// Types +interface OHLCVData { + timestamp: number; + open: number; + high: number; + low: number; + close: number; + volume: number; +} + +interface PairOHLCVResponse { + data: OHLCVData[]; + pair: { + baseToken: string; + quoteToken: string; + }; +} + +// Constants +const PAIR_OHLCV_KEYWORDS = [ + "pair ohlcv", + "pair candlestick", + "pair candles", + "pair chart", + "pair price history", + "pair historical data", + "pair market data", + "pair trading data", + "trading chart", + "price chart", + "market chart", + "candlestick chart", + "price action", + "market action", + "price movement", + "market movement", +] as const; + +// Helper functions +const containsPairOHLCVKeyword = (text: string): boolean => { + return PAIR_OHLCV_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getPairOHLCV = async ( + apiKey: string, + baseAddress: string, + quoteAddress: string, + timeframe: Timeframe, + chain: Chain, + limit: number +): Promise => { + try { + const params = new URLSearchParams({ + base_address: baseAddress, + quote_address: quoteAddress, + timeframe, + limit: limit.toString(), + }); + const url = `${BASE_URL}/defi/ohlcv_pair?${params.toString()}`; + + elizaLogger.info( + `Fetching OHLCV data for pair ${baseAddress}/${quoteAddress} with ${timeframe} timeframe on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { apiKey, chain }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching pair OHLCV data:", error.message); + } + return null; + } +}; + +const formatOHLCVData = (data: OHLCVData): string => { + const timestamp = formatTimestamp(data.timestamp); + const change = ((data.close - data.open) / data.open) * 100; + const trend = change >= 0 ? "šŸŸ¢" : "šŸ”“"; + + let response = `${trend} ${timestamp}\n`; + response += `ā€¢ Open: ${formatValue(data.open)}\n`; + response += `ā€¢ High: ${formatValue(data.high)}\n`; + response += `ā€¢ Low: ${formatValue(data.low)}\n`; + response += `ā€¢ Close: ${formatValue(data.close)}\n`; + response += `ā€¢ Volume: ${formatValue(data.volume)}\n`; + response += `ā€¢ Change: ${change >= 0 ? "+" : ""}${change.toFixed(2)}%`; + + return response; +}; + +const formatPairOHLCVResponse = ( + data: PairOHLCVResponse, + timeframe: Timeframe, + chain: Chain, + timeRange: { start: number; end: number } +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + const startDate = formatTimestamp(timeRange.start); + const endDate = formatTimestamp(timeRange.end); + + let response = `OHLCV Data for ${data.pair.baseToken}/${data.pair.quoteToken} pair on ${chainName}\n`; + response += `Timeframe: ${timeframe} (${startDate} to ${endDate})\n\n`; + + if (data.data.length === 0) { + return response + "No OHLCV data found for this pair."; + } + + // Calculate summary statistics + const latestPrice = data.data[data.data.length - 1].close; + const earliestPrice = data.data[0].open; + const priceChange = ((latestPrice - earliestPrice) / earliestPrice) * 100; + const totalVolume = data.data.reduce((sum, d) => sum + d.volume, 0); + const highestPrice = Math.max(...data.data.map((d) => d.high)); + const lowestPrice = Math.min(...data.data.map((d) => d.low)); + const averageVolume = totalVolume / data.data.length; + + response += `šŸ“Š Summary\n`; + response += `ā€¢ Current Price: ${formatValue(latestPrice)}\n`; + response += `ā€¢ Period Change: ${priceChange >= 0 ? "+" : ""}${priceChange.toFixed(2)}%\n`; + response += `ā€¢ Total Volume: ${formatValue(totalVolume)}\n`; + response += `ā€¢ Average Volume: ${formatValue(averageVolume)}\n`; + response += `ā€¢ Highest Price: ${formatValue(highestPrice)}\n`; + response += `ā€¢ Lowest Price: ${formatValue(lowestPrice)}\n`; + response += `ā€¢ Price Range: ${(((highestPrice - lowestPrice) / lowestPrice) * 100).toFixed(2)}%\n\n`; + + response += `šŸ“ˆ Recent Data\n`; + // Show only the last 5 entries + const recentData = data.data.slice(-5); + recentData.forEach((candle, index) => { + response += `${index + 1}. ${formatOHLCVData(candle)}\n\n`; + }); + + if (data.data.length > 5) { + response += `Showing last 5 of ${data.data.length} candles.`; + } + + return response; +}; + +export const pairOHLCVProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsPairOHLCVKeyword(messageText)) { + return null; + } + + const addresses = extractContractAddresses(messageText); + if (addresses.length !== 2) { + return null; + } + + const chain = extractChain(messageText); + const timeframe = extractTimeframe(messageText); + const timeRange = extractTimeRange(messageText); + const limit = extractLimit(messageText); + + elizaLogger.info( + `PAIR OHLCV provider activated for base ${addresses[0]} and quote ${addresses[1]} with ${timeframe} timeframe on ${chain}` + ); + + const ohlcvData = await getPairOHLCV( + apiKey, + addresses[0], + addresses[1], + timeframe, + chain, + limit + ); + + if (!ohlcvData) { + return null; + } + + return formatPairOHLCVResponse(ohlcvData, timeframe, chain, timeRange); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/defi/ohlcv-provider.ts b/packages/plugin-birdeye/src/providers/defi/ohlcv-provider.ts new file mode 100644 index 0000000000..259e1b4a0d --- /dev/null +++ b/packages/plugin-birdeye/src/providers/defi/ohlcv-provider.ts @@ -0,0 +1,256 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; + +// Types +interface OHLCVData { + timestamp: number; + open: number; + high: number; + low: number; + close: number; + volume: number; +} + +// Constants +const OHLCV_KEYWORDS = [ + "ohlc", + "ohlcv", + "candlestick", + "candle", + "chart", + "price history", + "historical", +] as const; + +const TIME_INTERVAL_KEYWORDS = { + "1m": ["1 minute", "1min", "1m"], + "3m": ["3 minutes", "3min", "3m"], + "5m": ["5 minutes", "5min", "5m"], + "15m": ["15 minutes", "15min", "15m"], + "30m": ["30 minutes", "30min", "30m"], + "1h": ["1 hour", "1hr", "1h"], + "2h": ["2 hours", "2hr", "2h"], + "4h": ["4 hours", "4hr", "4h"], + "6h": ["6 hours", "6hr", "6h"], + "12h": ["12 hours", "12hr", "12h"], + "1d": ["1 day", "daily", "1d"], + "1w": ["1 week", "weekly", "1w"], + "1mo": ["1 month", "monthly", "1mo"], +} as const; + +const CHAIN_KEYWORDS = [ + "solana", + "ethereum", + "arbitrum", + "avalanche", + "bsc", + "optimism", + "polygon", + "base", + "zksync", + "sui", +] as const; + +const BASE_URL = "https://public-api.birdeye.so"; + +// Helper functions +const containsOHLCVKeyword = (text: string): boolean => { + return OHLCV_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const extractTimeInterval = (text: string): string => { + const lowerText = text.toLowerCase(); + for (const [interval, keywords] of Object.entries(TIME_INTERVAL_KEYWORDS)) { + if (keywords.some((keyword) => lowerText.includes(keyword))) { + return interval; + } + } + return "1d"; // Default to daily if no interval specified +}; + +const extractChain = (text: string): string => { + const chain = CHAIN_KEYWORDS.find((chain) => + text.toLowerCase().includes(chain.toLowerCase()) + ); + return chain || "solana"; +}; + +const extractContractAddress = (text: string): string | null => { + const words = text.split(/\s+/); + + for (const word of words) { + // Ethereum-like addresses (0x...) + if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { + return word; + } + // Solana addresses (base58, typically 32-44 chars) + if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { + return word; + } + } + return null; +}; + +const getOHLCVData = async ( + apiKey: string, + contractAddress: string, + interval: string = "1d", + chain: string = "solana" +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + interval, + limit: "24", // Get last 24 periods + }); + const url = `${BASE_URL}/defi/ohlcv?${params.toString()}`; + + elizaLogger.info( + `Fetching OHLCV data for address ${contractAddress} on ${chain} with interval ${interval} from:`, + url + ); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + }, + }); + + if (!response.ok) { + if (response.status === 404) { + elizaLogger.warn( + `Token not found: ${contractAddress} on ${chain}` + ); + return null; + } + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.data; + } catch (error) { + elizaLogger.error("Error fetching OHLCV data:", error); + return null; + } +}; + +const formatNumber = (num: number): string => { + if (!num && num !== 0) return "N/A"; + return num < 0.01 + ? num.toExponential(2) + : num.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 6, + }); +}; + +const formatVolume = (volume: number): string => { + if (volume >= 1_000_000_000) { + return `$${(volume / 1_000_000_000).toFixed(2)}B`; + } + if (volume >= 1_000_000) { + return `$${(volume / 1_000_000).toFixed(2)}M`; + } + if (volume >= 1_000) { + return `$${(volume / 1_000).toFixed(2)}K`; + } + return `$${volume.toFixed(2)}`; +}; + +const formatOHLCVResponse = ( + data: OHLCVData[], + interval: string, + chain: string +): string => { + if (data.length === 0) { + return "No OHLCV data available for the specified period."; + } + + // Sort data by timestamp in ascending order + const sortedData = [...data].sort((a, b) => a.timestamp - b.timestamp); + const latestData = sortedData[sortedData.length - 1]; + + let response = `OHLCV Data (${interval}) on ${chain.charAt(0).toUpperCase() + chain.slice(1)}:\n\n`; + + // Latest price information + response += `šŸ“Š Latest Candle (${new Date(latestData.timestamp * 1000).toLocaleString()})\n`; + response += `ā€¢ Open: $${formatNumber(latestData.open)}\n`; + response += `ā€¢ High: $${formatNumber(latestData.high)}\n`; + response += `ā€¢ Low: $${formatNumber(latestData.low)}\n`; + response += `ā€¢ Close: $${formatNumber(latestData.close)}\n`; + response += `ā€¢ Volume: ${formatVolume(latestData.volume)}\n`; + + // Price change statistics + const priceChange = latestData.close - latestData.open; + const priceChangePercent = (priceChange / latestData.open) * 100; + const trend = priceChange >= 0 ? "šŸ“ˆ" : "šŸ“‰"; + + response += `\n${trend} Period Change\n`; + response += `ā€¢ Price Change: $${formatNumber(priceChange)} (${priceChangePercent.toFixed(2)}%)\n`; + + // Volume analysis + const totalVolume = sortedData.reduce( + (sum, candle) => sum + candle.volume, + 0 + ); + const avgVolume = totalVolume / sortedData.length; + + response += `\nšŸ“Š Volume Analysis\n`; + response += `ā€¢ Total Volume: ${formatVolume(totalVolume)}\n`; + response += `ā€¢ Average Volume: ${formatVolume(avgVolume)}\n`; + + return response; +}; + +export const ohlcvProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsOHLCVKeyword(messageText)) { + return null; + } + + const contractAddress = extractContractAddress(messageText); + if (!contractAddress) { + return null; + } + + const chain = extractChain(messageText); + const interval = extractTimeInterval(messageText); + + elizaLogger.info( + `OHLCV provider activated for address ${contractAddress} on ${chain} with interval ${interval}` + ); + + const ohlcvData = await getOHLCVData( + apiKey, + contractAddress, + interval, + chain + ); + + if (!ohlcvData) { + return null; + } + + return formatOHLCVResponse(ohlcvData, interval, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/defi/pair-trades-provider.ts b/packages/plugin-birdeye/src/providers/defi/pair-trades-provider.ts new file mode 100644 index 0000000000..d0eefb6da9 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/defi/pair-trades-provider.ts @@ -0,0 +1,245 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + extractChain, + extractContractAddresses, + extractLimit, + formatTimestamp, + formatValue, + makeApiRequest, + shortenAddress, +} from "../utils"; + +// Types +interface PairTrade { + timestamp: number; + price: number; + volume: number; + side: "buy" | "sell"; + source: string; + txHash: string; + buyer?: string; + seller?: string; + baseToken: string; + quoteToken: string; +} + +interface PairTradesResponse { + trades: PairTrade[]; + totalCount: number; + pair: { + baseToken: string; + quoteToken: string; + }; +} + +// Constants +const PAIR_TRADES_KEYWORDS = [ + "pair trades", + "pair swaps", + "pair transactions", + "pair activity", + "pair orders", + "pair executions", + "pair trading", + "pair market activity", + "pair exchange activity", + "pair trading history", + "pair market history", + "pair exchange history", + "trading pair activity", + "trading pair history", + "base/quote trades", + "base/quote activity", +] as const; + +// Helper functions +const containsPairTradesKeyword = (text: string): boolean => { + return PAIR_TRADES_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getPairTrades = async ( + apiKey: string, + baseAddress: string, + quoteAddress: string, + chain: Chain, + limit: number +): Promise => { + try { + const params = new URLSearchParams({ + base_address: baseAddress, + quote_address: quoteAddress, + limit: limit.toString(), + }); + const url = `${BASE_URL}/defi/trades_pair?${params.toString()}`; + + elizaLogger.info( + `Fetching pair trades for base ${baseAddress} and quote ${quoteAddress} on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { apiKey, chain }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching pair trades:", error.message); + } + return null; + } +}; + +const formatPairTrade = (trade: PairTrade): string => { + const timestamp = formatTimestamp(trade.timestamp); + const side = trade.side === "buy" ? "šŸŸ¢ Buy" : "šŸ”“ Sell"; + + let response = `${side} - ${timestamp}\n`; + response += `ā€¢ Price: ${formatValue(trade.price)}\n`; + response += `ā€¢ Volume: ${formatValue(trade.volume)}\n`; + response += `ā€¢ Source: ${trade.source}\n`; + if (trade.buyer && trade.seller) { + response += `ā€¢ Buyer: ${shortenAddress(trade.buyer)}\n`; + response += `ā€¢ Seller: ${shortenAddress(trade.seller)}\n`; + } + response += `ā€¢ Tx: ${shortenAddress(trade.txHash)}`; + + return response; +}; + +const formatPairTradesResponse = ( + data: PairTradesResponse, + chain: Chain +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + + let response = `Recent Trades for ${data.pair.baseToken}/${data.pair.quoteToken} pair on ${chainName}\n\n`; + + if (data.trades.length === 0) { + return response + "No trades found."; + } + + // Calculate summary statistics + const totalVolume = data.trades.reduce((sum, t) => sum + t.volume, 0); + const averageVolume = totalVolume / data.trades.length; + const buyCount = data.trades.filter((t) => t.side === "buy").length; + const buyRatio = (buyCount / data.trades.length) * 100; + const averagePrice = + data.trades.reduce((sum, t) => sum + t.price, 0) / data.trades.length; + const priceChange = + ((data.trades[data.trades.length - 1].price - data.trades[0].price) / + data.trades[0].price) * + 100; + const highestPrice = Math.max(...data.trades.map((t) => t.price)); + const lowestPrice = Math.min(...data.trades.map((t) => t.price)); + const priceRange = ((highestPrice - lowestPrice) / lowestPrice) * 100; + + response += `šŸ“Š Summary\n`; + response += `ā€¢ Total Trades: ${data.trades.length}\n`; + response += `ā€¢ Total Volume: ${formatValue(totalVolume)}\n`; + response += `ā€¢ Average Volume: ${formatValue(averageVolume)}\n`; + response += `ā€¢ Buy/Sell Ratio: ${buyRatio.toFixed(1)}% buys\n`; + response += `ā€¢ Average Price: ${formatValue(averagePrice)}\n`; + response += `ā€¢ Price Change: ${priceChange >= 0 ? "+" : ""}${priceChange.toFixed(2)}%\n`; + response += `ā€¢ Price Range: ${priceRange.toFixed(2)}%\n\n`; + + // Add market analysis + const volatility = priceRange / Math.sqrt(data.trades.length); + const volumePerTrade = totalVolume / data.trades.length; + let marketAnalysis = ""; + + if (data.trades.length < 5) { + marketAnalysis = "Insufficient data for detailed analysis"; + } else { + // Analyze trading activity + const activityLevel = + data.trades.length > 20 + ? "high" + : data.trades.length > 10 + ? "moderate" + : "low"; + const volumeLevel = + volumePerTrade > averageVolume * 2 + ? "high" + : volumePerTrade > averageVolume + ? "moderate" + : "low"; + const volatilityLevel = + volatility > 5 ? "high" : volatility > 2 ? "moderate" : "low"; + const trend = + Math.abs(priceChange) < 1 + ? "sideways" + : priceChange > 0 + ? "upward" + : "downward"; + + marketAnalysis = `Market shows ${activityLevel} trading activity with ${volumeLevel} volume per trade. `; + marketAnalysis += `${volatilityLevel.charAt(0).toUpperCase() + volatilityLevel.slice(1)} volatility with a ${trend} price trend.`; + } + + response += `šŸ“ˆ Market Analysis\n`; + response += `ā€¢ ${marketAnalysis}\n\n`; + + response += `šŸ”„ Recent Trades\n`; + data.trades.forEach((trade, index) => { + response += `${index + 1}. ${formatPairTrade(trade)}\n\n`; + }); + + if (data.totalCount > data.trades.length) { + response += `Showing ${data.trades.length} of ${data.totalCount} total trades.`; + } + + return response; +}; + +export const pairTradesProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsPairTradesKeyword(messageText)) { + return null; + } + + const addresses = extractContractAddresses(messageText); + if (addresses.length !== 2) { + return null; + } + + const chain = extractChain(messageText); + const limit = extractLimit(messageText); + + elizaLogger.info( + `PAIR TRADES provider activated for base ${addresses[0]} and quote ${addresses[1]} on ${chain}` + ); + + const tradesData = await getPairTrades( + apiKey, + addresses[0], + addresses[1], + chain, + limit + ); + + if (!tradesData) { + return null; + } + + return formatPairTradesResponse(tradesData, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/defi/pair-trades-seek-provider.ts b/packages/plugin-birdeye/src/providers/defi/pair-trades-seek-provider.ts new file mode 100644 index 0000000000..7d293d1d68 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/defi/pair-trades-seek-provider.ts @@ -0,0 +1,266 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + extractChain, + extractContractAddresses, + extractLimit, + extractTimeRange, + formatTimestamp, + formatValue, + makeApiRequest, + shortenAddress, +} from "../utils"; + +// Types +interface PairTrade { + timestamp: number; + price: number; + volume: number; + side: "buy" | "sell"; + source: string; + txHash: string; + buyer?: string; + seller?: string; + baseToken: string; + quoteToken: string; +} + +interface PairTradesResponse { + trades: PairTrade[]; + totalCount: number; + pair: { + baseToken: string; + quoteToken: string; + }; +} + +// Constants +const PAIR_TRADE_KEYWORDS = [ + "pair trades", + "pair trading", + "pair transactions", + "pair swaps", + "pair buys", + "pair sells", + "pair orders", + "pair executions", + "pair trade history", + "pair trading history", + "pair recent trades", + "pair market activity", + "pair trading activity", + "pair market trades", + "pair exchange history", + "trading pair history", + "trading pair activity", + "base/quote trades", + "base/quote activity", +] as const; + +// Helper functions +const containsPairTradeKeyword = (text: string): boolean => { + return PAIR_TRADE_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getPairTradesByTime = async ( + apiKey: string, + baseAddress: string, + quoteAddress: string, + timestamp: number, + chain: Chain, + limit: number +): Promise => { + try { + const params = new URLSearchParams({ + base_address: baseAddress, + quote_address: quoteAddress, + timestamp: timestamp.toString(), + limit: limit.toString(), + }); + const url = `${BASE_URL}/defi/trades_pair_seek_time?${params.toString()}`; + + elizaLogger.info( + `Fetching pair trades for base ${baseAddress} and quote ${quoteAddress} since ${new Date( + timestamp * 1000 + ).toLocaleString()} on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { apiKey, chain }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error( + "Error fetching pair trades by time:", + error.message + ); + } + return null; + } +}; + +const formatPairTrade = (trade: PairTrade): string => { + const timestamp = formatTimestamp(trade.timestamp); + const side = trade.side === "buy" ? "šŸŸ¢ Buy" : "šŸ”“ Sell"; + + let response = `${side} - ${timestamp}\n`; + response += `ā€¢ Price: ${formatValue(trade.price)}\n`; + response += `ā€¢ Volume: ${formatValue(trade.volume)}\n`; + response += `ā€¢ Source: ${trade.source}\n`; + if (trade.buyer && trade.seller) { + response += `ā€¢ Buyer: ${shortenAddress(trade.buyer)}\n`; + response += `ā€¢ Seller: ${shortenAddress(trade.seller)}\n`; + } + response += `ā€¢ Tx: ${shortenAddress(trade.txHash)}`; + + return response; +}; + +const formatPairTradesResponse = ( + data: PairTradesResponse, + timeRange: { start: number; end: number }, + chain: Chain +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + const startDate = formatTimestamp(timeRange.start); + const endDate = formatTimestamp(timeRange.end); + + let response = `Trade History for ${data.pair.baseToken}/${data.pair.quoteToken} pair on ${chainName}\n`; + response += `Period: ${startDate} to ${endDate}\n\n`; + + if (data.trades.length === 0) { + return response + "No trades found in this time period."; + } + + // Calculate summary statistics + const totalVolume = data.trades.reduce((sum, t) => sum + t.volume, 0); + const averageVolume = totalVolume / data.trades.length; + const buyCount = data.trades.filter((t) => t.side === "buy").length; + const buyRatio = (buyCount / data.trades.length) * 100; + const averagePrice = + data.trades.reduce((sum, t) => sum + t.price, 0) / data.trades.length; + const priceChange = + ((data.trades[data.trades.length - 1].price - data.trades[0].price) / + data.trades[0].price) * + 100; + const highestPrice = Math.max(...data.trades.map((t) => t.price)); + const lowestPrice = Math.min(...data.trades.map((t) => t.price)); + const priceRange = ((highestPrice - lowestPrice) / lowestPrice) * 100; + + response += `šŸ“Š Summary\n`; + response += `ā€¢ Total Trades: ${data.trades.length}\n`; + response += `ā€¢ Total Volume: ${formatValue(totalVolume)}\n`; + response += `ā€¢ Average Volume: ${formatValue(averageVolume)}\n`; + response += `ā€¢ Buy/Sell Ratio: ${buyRatio.toFixed(1)}% buys\n`; + response += `ā€¢ Average Price: ${formatValue(averagePrice)}\n`; + response += `ā€¢ Price Change: ${priceChange >= 0 ? "+" : ""}${priceChange.toFixed(2)}%\n`; + response += `ā€¢ Price Range: ${priceRange.toFixed(2)}%\n\n`; + + // Add market analysis + const volatility = priceRange / Math.sqrt(data.trades.length); + const volumePerTrade = totalVolume / data.trades.length; + let marketAnalysis = ""; + + if (data.trades.length < 5) { + marketAnalysis = "Insufficient data for detailed analysis"; + } else { + // Analyze trading activity + const activityLevel = + data.trades.length > 20 + ? "high" + : data.trades.length > 10 + ? "moderate" + : "low"; + const volumeLevel = + volumePerTrade > averageVolume * 2 + ? "high" + : volumePerTrade > averageVolume + ? "moderate" + : "low"; + const volatilityLevel = + volatility > 5 ? "high" : volatility > 2 ? "moderate" : "low"; + const trend = + Math.abs(priceChange) < 1 + ? "sideways" + : priceChange > 0 + ? "upward" + : "downward"; + + marketAnalysis = `Market shows ${activityLevel} trading activity with ${volumeLevel} volume per trade. `; + marketAnalysis += `${volatilityLevel.charAt(0).toUpperCase() + volatilityLevel.slice(1)} volatility with a ${trend} price trend.`; + } + + response += `šŸ“ˆ Market Analysis\n`; + response += `ā€¢ ${marketAnalysis}\n\n`; + + response += `šŸ”„ Recent Trades\n`; + data.trades.forEach((trade, index) => { + response += `${index + 1}. ${formatPairTrade(trade)}\n\n`; + }); + + if (data.totalCount > data.trades.length) { + response += `Showing ${data.trades.length} of ${data.totalCount} total trades.`; + } + + return response; +}; + +export const pairTradesSeekProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsPairTradeKeyword(messageText)) { + return null; + } + + const addresses = extractContractAddresses(messageText); + if (addresses.length !== 2) { + return null; + } + + const chain = extractChain(messageText); + const timeRange = extractTimeRange(messageText); + const limit = extractLimit(messageText); + + elizaLogger.info( + `PAIR TRADES SEEK provider activated for base ${addresses[0]} and quote ${addresses[1]} from ${new Date( + timeRange.start * 1000 + ).toLocaleString()} to ${new Date( + timeRange.end * 1000 + ).toLocaleString()} on ${chain}` + ); + + const tradesData = await getPairTradesByTime( + apiKey, + addresses[0], + addresses[1], + timeRange.start, + chain, + limit + ); + + if (!tradesData) { + return null; + } + + return formatPairTradesResponse(tradesData, timeRange, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/defi/price-history-provider.ts b/packages/plugin-birdeye/src/providers/defi/price-history-provider.ts new file mode 100644 index 0000000000..69c2275488 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/defi/price-history-provider.ts @@ -0,0 +1,230 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + extractChain, + extractContractAddresses, + extractTimeRange, + formatTimestamp, + formatValue, + makeApiRequest, +} from "../utils"; + +// Types +interface PriceHistoryData { + price: number; + timestamp: number; + volume?: number; +} + +interface PriceHistoryResponse { + data: PriceHistoryData[]; + token: string; +} + +// Constants +const PRICE_HISTORY_KEYWORDS = [ + "price history", + "historical price", + "price chart", + "price trend", + "price movement", + "price changes", + "price over time", + "price timeline", + "price performance", + "price data", + "historical data", + "price analysis", + "price tracking", + "price evolution", +] as const; + +// Helper functions +const containsPriceHistoryKeyword = (text: string): boolean => { + return PRICE_HISTORY_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getPriceHistory = async ( + apiKey: string, + contractAddress: string, + startTime: number, + endTime: number, + chain: Chain +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + time_from: startTime.toString(), + time_to: endTime.toString(), + }); + const url = `${BASE_URL}/defi/price_history_unix?${params.toString()}`; + + elizaLogger.info( + `Fetching price history for token ${contractAddress} from ${new Date( + startTime * 1000 + ).toLocaleString()} to ${new Date( + endTime * 1000 + ).toLocaleString()} on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { + apiKey, + chain, + }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching price history:", error.message); + } + return null; + } +}; + +const formatPriceHistoryResponse = ( + data: PriceHistoryResponse, + timeRange: { start: number; end: number }, + chain: Chain +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + const startDate = formatTimestamp(timeRange.start); + const endDate = formatTimestamp(timeRange.end); + + let response = `Price History for ${data.token} on ${chainName}\n`; + response += `Period: ${startDate} to ${endDate}\n\n`; + + if (data.data.length === 0) { + return response + "No price data found for this period."; + } + + // Calculate summary statistics + const prices = data.data.map((d) => d.price); + const volumes = data.data.map((d) => d.volume || 0); + const startPrice = data.data[0].price; + const endPrice = data.data[data.data.length - 1].price; + const priceChange = ((endPrice - startPrice) / startPrice) * 100; + const highestPrice = Math.max(...prices); + const lowestPrice = Math.min(...prices); + const averagePrice = prices.reduce((a, b) => a + b, 0) / prices.length; + const totalVolume = volumes.reduce((a, b) => a + b, 0); + const volatility = ((highestPrice - lowestPrice) / averagePrice) * 100; + + response += `šŸ“Š Summary\n`; + response += `ā€¢ Start Price: ${formatValue(startPrice)}\n`; + response += `ā€¢ End Price: ${formatValue(endPrice)}\n`; + response += `ā€¢ Price Change: ${priceChange >= 0 ? "+" : ""}${priceChange.toFixed(2)}%\n`; + response += `ā€¢ Highest Price: ${formatValue(highestPrice)}\n`; + response += `ā€¢ Lowest Price: ${formatValue(lowestPrice)}\n`; + response += `ā€¢ Average Price: ${formatValue(averagePrice)}\n`; + if (totalVolume > 0) { + response += `ā€¢ Total Volume: ${formatValue(totalVolume)}\n`; + } + response += `ā€¢ Volatility: ${volatility.toFixed(2)}%\n\n`; + + // Add trend analysis + const trendStrength = Math.abs(priceChange); + let trendAnalysis = ""; + if (trendStrength < 1) { + trendAnalysis = "Price has remained relatively stable"; + } else if (trendStrength < 5) { + trendAnalysis = + priceChange > 0 + ? "Price shows slight upward movement" + : "Price shows slight downward movement"; + } else if (trendStrength < 10) { + trendAnalysis = + priceChange > 0 + ? "Price demonstrates moderate upward trend" + : "Price demonstrates moderate downward trend"; + } else { + trendAnalysis = + priceChange > 0 + ? "Price exhibits strong upward momentum" + : "Price exhibits strong downward momentum"; + } + + response += `šŸ“ˆ Trend Analysis\n`; + response += `ā€¢ ${trendAnalysis}\n`; + response += `ā€¢ Volatility is ${volatility < 10 ? "low" : volatility < 25 ? "moderate" : "high"}\n\n`; + + // Show key price points + response += `šŸ”‘ Key Price Points\n`; + const keyPoints = [ + { label: "Start", ...data.data[0] }, + { + label: "High", + price: highestPrice, + timestamp: data.data[prices.indexOf(highestPrice)].timestamp, + }, + { + label: "Low", + price: lowestPrice, + timestamp: data.data[prices.indexOf(lowestPrice)].timestamp, + }, + { label: "End", ...data.data[data.data.length - 1] }, + ]; + + keyPoints.forEach((point) => { + response += `ā€¢ ${point.label}: ${formatValue(point.price)} (${formatTimestamp(point.timestamp)})\n`; + }); + + return response; +}; + +export const priceHistoryProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsPriceHistoryKeyword(messageText)) { + return null; + } + + const addresses = extractContractAddresses(messageText); + if (addresses.length === 0) { + return null; + } + + const chain = extractChain(messageText); + const timeRange = extractTimeRange(messageText); + + elizaLogger.info( + `PRICE HISTORY provider activated for token ${addresses[0]} from ${new Date( + timeRange.start * 1000 + ).toLocaleString()} to ${new Date( + timeRange.end * 1000 + ).toLocaleString()} on ${chain}` + ); + + const priceData = await getPriceHistory( + apiKey, + addresses[0], + timeRange.start, + timeRange.end, + chain + ); + + if (!priceData) { + return null; + } + + return formatPriceHistoryResponse(priceData, timeRange, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/defi/price-multiple-provider.ts b/packages/plugin-birdeye/src/providers/defi/price-multiple-provider.ts new file mode 100644 index 0000000000..ad4c95bbde --- /dev/null +++ b/packages/plugin-birdeye/src/providers/defi/price-multiple-provider.ts @@ -0,0 +1,200 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; + +// Types +interface TokenPrice { + price: number; + timestamp: number; + token: string; + priceChange24h?: number; + priceChange24hPercent?: number; +} + +interface MultiPriceResponse { + [tokenAddress: string]: TokenPrice; +} + +// Constants +const PRICE_KEYWORDS = [ + "price", + "prices", + "cost", + "worth", + "value", + "compare", + "multiple", + "several", + "many", + "list of", + "these tokens", + "their prices", +] as const; + +const CHAIN_KEYWORDS = [ + "solana", + "ethereum", + "arbitrum", + "avalanche", + "bsc", + "optimism", + "polygon", + "base", + "zksync", + "sui", +] as const; + +const BASE_URL = "https://public-api.birdeye.so"; + +// Helper functions +const containsPriceKeyword = (text: string): boolean => { + return PRICE_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const extractChain = (text: string): string => { + const chain = CHAIN_KEYWORDS.find((chain) => + text.toLowerCase().includes(chain.toLowerCase()) + ); + return chain || "solana"; +}; + +const extractContractAddresses = (text: string): string[] => { + const words = text.split(/\s+/); + const addresses: string[] = []; + + for (const word of words) { + // Ethereum-like addresses (0x...) + if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { + addresses.push(word); + } + // Solana addresses (base58, typically 32-44 chars) + if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { + addresses.push(word); + } + } + return addresses; +}; + +const getMultiplePrices = async ( + apiKey: string, + addresses: string[], + chain: string = "solana" +): Promise => { + try { + const params = new URLSearchParams({ + tokens: addresses.join(","), + }); + const url = `${BASE_URL}/defi/price_multiple?${params.toString()}`; + + elizaLogger.info( + `Fetching prices for ${addresses.length} tokens on ${chain} from:`, + url + ); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.data; + } catch (error) { + elizaLogger.error("Error fetching multiple prices:", error); + return null; + } +}; + +const formatNumber = (num: number): string => { + if (!num && num !== 0) return "N/A"; + return num < 0.01 + ? num.toExponential(2) + : num.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); +}; + +const formatPriceResponse = ( + prices: MultiPriceResponse, + chain: string +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + let response = `Token Prices on ${chainName}:\n\n`; + + const sortedTokens = Object.entries(prices).sort((a, b) => { + const priceA = a[1].price || 0; + const priceB = b[1].price || 0; + return priceB - priceA; + }); + + sortedTokens.forEach(([address, data]) => { + const timestamp = new Date(data.timestamp * 1000).toLocaleString(); + response += `${data.token} (${address.slice(0, 6)}...${address.slice(-4)}):\n`; + response += `ā€¢ Price: $${formatNumber(data.price)}\n`; + + if (data.priceChange24h !== undefined) { + const changeSymbol = data.priceChange24h >= 0 ? "šŸ“ˆ" : "šŸ“‰"; + response += `ā€¢ 24h Change: ${changeSymbol} $${formatNumber(Math.abs(data.priceChange24h))} `; + if (data.priceChange24hPercent !== undefined) { + response += `(${data.priceChange24hPercent.toFixed(2)}%)`; + } + response += "\n"; + } + + response += `ā€¢ Last Updated: ${timestamp}\n\n`; + }); + + return response; +}; + +export const priceMultipleProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsPriceKeyword(messageText)) { + return null; + } + + const addresses = extractContractAddresses(messageText); + if (addresses.length < 2) { + // If less than 2 addresses found, let the single price provider handle it + return null; + } + + const chain = extractChain(messageText); + + elizaLogger.info( + `MULTIPLE PRICE provider activated for ${addresses.length} addresses on ${chain}` + ); + + const priceData = await getMultiplePrices(apiKey, addresses, chain); + + if (!priceData) { + return null; + } + + return formatPriceResponse(priceData, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/defi/price-provider.ts b/packages/plugin-birdeye/src/providers/defi/price-provider.ts new file mode 100644 index 0000000000..f7c6cd45a8 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/defi/price-provider.ts @@ -0,0 +1,175 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; + +// Types +interface PriceData { + price: number; + timestamp: number; + token: string; + priceChange24h?: number; + priceChange24hPercent?: number; +} + +// Constants +const PRICE_KEYWORDS = [ + "price", + "cost", + "worth", + "value", + "rate", + "quote", + "how much", +] as const; + +const CHAIN_KEYWORDS = [ + "solana", + "ethereum", + "arbitrum", + "avalanche", + "bsc", + "optimism", + "polygon", + "base", + "zksync", + "sui", +] as const; + +const BASE_URL = "https://public-api.birdeye.so"; + +// Helper functions +const containsPriceKeyword = (text: string): boolean => { + return PRICE_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const extractChain = (text: string): string => { + const chain = CHAIN_KEYWORDS.find((chain) => + text.toLowerCase().includes(chain.toLowerCase()) + ); + return chain || "solana"; +}; + +const extractContractAddress = (text: string): string | null => { + const words = text.split(/\s+/); + + for (const word of words) { + // Ethereum-like addresses (0x...) + if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { + return word; + } + // Solana addresses (base58, typically 32-44 chars) + if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { + return word; + } + } + return null; +}; + +const getTokenPrice = async ( + apiKey: string, + contractAddress: string, + chain: string = "solana" +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + }); + const url = `${BASE_URL}/defi/price?${params.toString()}`; + + elizaLogger.info( + `Fetching price for address ${contractAddress} on ${chain} from:`, + url + ); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + }, + }); + + if (!response.ok) { + if (response.status === 404) { + elizaLogger.warn( + `Token not found: ${contractAddress} on ${chain}` + ); + return null; + } + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.data; + } catch (error) { + elizaLogger.error("Error fetching token price:", error); + return null; + } +}; + +const formatPriceResponse = (price: PriceData, chain: string): string => { + const timestamp = new Date(price.timestamp * 1000).toLocaleString(); + const priceFormatted = + price.price < 0.01 + ? price.price.toExponential(2) + : price.price.toFixed(2); + + let response = `Price for ${price.token} on ${chain.charAt(0).toUpperCase() + chain.slice(1)}:\n\n`; + response += `ā€¢ Current Price: $${priceFormatted}\n`; + + if (price.priceChange24h !== undefined) { + response += `ā€¢ 24h Change: $${price.priceChange24h.toFixed(2)}\n`; + } + + if (price.priceChange24hPercent !== undefined) { + response += `ā€¢ 24h Change %: ${price.priceChange24hPercent.toFixed(2)}%\n`; + } + + response += `ā€¢ Last Updated: ${timestamp}`; + + return response; +}; + +export const priceProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsPriceKeyword(messageText)) { + return null; + } + + const contractAddress = extractContractAddress(messageText); + if (!contractAddress) { + return null; + } + + const chain = extractChain(messageText); + + elizaLogger.info( + `PRICE provider activated for address ${contractAddress} on ${chain}` + ); + + const priceData = await getTokenPrice(apiKey, contractAddress, chain); + + if (!priceData) { + return null; + } + + return formatPriceResponse(priceData, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/defi/price-volume-provider.ts b/packages/plugin-birdeye/src/providers/defi/price-volume-provider.ts new file mode 100644 index 0000000000..1448fbb47f --- /dev/null +++ b/packages/plugin-birdeye/src/providers/defi/price-volume-provider.ts @@ -0,0 +1,234 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + extractChain, + extractContractAddresses, + formatPercentChange, + formatTimestamp, + formatValue, + makeApiRequest, +} from "../utils"; + +// Types +interface PriceVolumeData { + price: number; + volume24h: number; + timestamp: number; + token: string; + priceChange24h?: number; + priceChange24hPercent?: number; + volumeChange24h?: number; + volumeChange24hPercent?: number; +} + +interface MultiPriceVolumeResponse { + [tokenAddress: string]: PriceVolumeData; +} + +// Constants +const PRICE_VOLUME_KEYWORDS = [ + "price and volume", + "volume and price", + "trading volume", + "market activity", + "market data", + "trading data", + "market stats", + "trading stats", + "market metrics", + "trading metrics", +] as const; + +// Helper functions +const containsPriceVolumeKeyword = (text: string): boolean => { + return PRICE_VOLUME_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getSinglePriceVolume = async ( + apiKey: string, + contractAddress: string, + chain: Chain +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + }); + const url = `${BASE_URL}/defi/price_volume_single?${params.toString()}`; + + elizaLogger.info( + `Fetching price/volume data for address ${contractAddress} on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { apiKey, chain }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error( + "Error fetching price/volume data:", + error.message + ); + } + return null; + } +}; + +const getMultiplePriceVolume = async ( + apiKey: string, + addresses: string[], + chain: Chain +): Promise => { + try { + const url = `${BASE_URL}/defi/price_volume_multi`; + + elizaLogger.info( + `Fetching price/volume data for ${addresses.length} tokens on ${chain}` + ); + + return await makeApiRequest(url, { + apiKey, + chain, + method: "POST", + body: { addresses }, + }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error( + "Error fetching multiple price/volume data:", + error.message + ); + } + return null; + } +}; + +const formatSinglePriceVolumeResponse = ( + data: PriceVolumeData, + chain: Chain +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + const timestamp = formatTimestamp(data.timestamp); + const priceFormatted = formatValue(data.price); + + let response = `Price & Volume Data for ${data.token} on ${chainName}:\n\n`; + + response += `šŸ’° Price Metrics\n`; + response += `ā€¢ Current Price: ${priceFormatted}\n`; + if (data.priceChange24h !== undefined) { + response += `ā€¢ 24h Price Change: ${formatValue(data.priceChange24h)} (${formatPercentChange(data.priceChange24hPercent)})\n`; + } + + response += `\nšŸ“Š Volume Metrics\n`; + response += `ā€¢ 24h Volume: ${formatValue(data.volume24h)}\n`; + if (data.volumeChange24h !== undefined) { + response += `ā€¢ 24h Volume Change: ${formatValue(data.volumeChange24h)} (${formatPercentChange(data.volumeChange24hPercent)})\n`; + } + + response += `\nā° Last Updated: ${timestamp}`; + + return response; +}; + +const formatMultiplePriceVolumeResponse = ( + data: MultiPriceVolumeResponse, + chain: Chain +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + let response = `Price & Volume Data on ${chainName}:\n\n`; + + // Sort tokens by volume + const sortedTokens = Object.entries(data).sort((a, b) => { + const volumeA = a[1].volume24h || 0; + const volumeB = b[1].volume24h || 0; + return volumeB - volumeA; + }); + + sortedTokens.forEach(([address, tokenData]) => { + const timestamp = formatTimestamp(tokenData.timestamp); + const priceFormatted = formatValue(tokenData.price); + + response += `${tokenData.token} (${address.slice(0, 6)}...${address.slice(-4)})\n`; + response += `ā€¢ Price: ${priceFormatted}`; + if (tokenData.priceChange24hPercent !== undefined) { + response += ` (${formatPercentChange(tokenData.priceChange24hPercent)})`; + } + response += `\n`; + response += `ā€¢ Volume: ${formatValue(tokenData.volume24h)}`; + if (tokenData.volumeChange24hPercent !== undefined) { + response += ` (${formatPercentChange(tokenData.volumeChange24hPercent)})`; + } + response += `\n`; + response += `ā€¢ Updated: ${timestamp}\n\n`; + }); + + return response; +}; + +export const priceVolumeProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsPriceVolumeKeyword(messageText)) { + return null; + } + + const addresses = extractContractAddresses(messageText); + if (addresses.length === 0) { + return null; + } + + const chain = extractChain(messageText); + + if (addresses.length === 1) { + elizaLogger.info( + `PRICE/VOLUME provider activated for address ${addresses[0]} on ${chain}` + ); + + const priceVolumeData = await getSinglePriceVolume( + apiKey, + addresses[0], + chain + ); + + if (!priceVolumeData) { + return null; + } + + return formatSinglePriceVolumeResponse(priceVolumeData, chain); + } else { + elizaLogger.info( + `MULTIPLE PRICE/VOLUME provider activated for ${addresses.length} addresses on ${chain}` + ); + + const priceVolumeData = await getMultiplePriceVolume( + apiKey, + addresses, + chain + ); + + if (!priceVolumeData) { + return null; + } + + return formatMultiplePriceVolumeResponse(priceVolumeData, chain); + } + }, +}; diff --git a/packages/plugin-birdeye/src/providers/defi/token-trades-provider.ts b/packages/plugin-birdeye/src/providers/defi/token-trades-provider.ts new file mode 100644 index 0000000000..d580eb0df9 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/defi/token-trades-provider.ts @@ -0,0 +1,236 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + extractChain, + extractContractAddresses, + extractLimit, + formatTimestamp, + formatValue, + makeApiRequest, + shortenAddress, +} from "../utils"; + +// Types +interface Trade { + timestamp: number; + price: number; + volume: number; + side: "buy" | "sell"; + source: string; + txHash: string; + buyer?: string; + seller?: string; +} + +interface TokenTradesResponse { + trades: Trade[]; + totalCount: number; + token: string; +} + +// Constants +const TOKEN_TRADES_KEYWORDS = [ + "token trades", + "token swaps", + "token transactions", + "token activity", + "token orders", + "token executions", + "token trading", + "token market activity", + "token exchange activity", + "token trading history", + "token market history", + "token exchange history", +] as const; + +// Helper functions +const containsTokenTradesKeyword = (text: string): boolean => { + return TOKEN_TRADES_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getTokenTrades = async ( + apiKey: string, + contractAddress: string, + chain: Chain, + limit: number +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + limit: limit.toString(), + }); + const url = `${BASE_URL}/defi/trades_token?${params.toString()}`; + + elizaLogger.info( + `Fetching token trades for ${contractAddress} on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { + apiKey, + chain, + }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching token trades:", error.message); + } + return null; + } +}; + +const formatTrade = (trade: Trade): string => { + const timestamp = formatTimestamp(trade.timestamp); + const side = trade.side === "buy" ? "šŸŸ¢ Buy" : "šŸ”“ Sell"; + + let response = `${side} - ${timestamp}\n`; + response += `ā€¢ Price: ${formatValue(trade.price)}\n`; + response += `ā€¢ Volume: ${formatValue(trade.volume)}\n`; + response += `ā€¢ Source: ${trade.source}\n`; + if (trade.buyer && trade.seller) { + response += `ā€¢ Buyer: ${shortenAddress(trade.buyer)}\n`; + response += `ā€¢ Seller: ${shortenAddress(trade.seller)}\n`; + } + response += `ā€¢ Tx: ${shortenAddress(trade.txHash)}`; + + return response; +}; + +const formatTokenTradesResponse = ( + data: TokenTradesResponse, + chain: Chain +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + + let response = `Recent Trades for ${data.token} on ${chainName}\n\n`; + + if (data.trades.length === 0) { + return response + "No trades found."; + } + + // Calculate summary statistics + const totalVolume = data.trades.reduce((sum, t) => sum + t.volume, 0); + const averageVolume = totalVolume / data.trades.length; + const buyCount = data.trades.filter((t) => t.side === "buy").length; + const buyRatio = (buyCount / data.trades.length) * 100; + const averagePrice = + data.trades.reduce((sum, t) => sum + t.price, 0) / data.trades.length; + const priceChange = + ((data.trades[data.trades.length - 1].price - data.trades[0].price) / + data.trades[0].price) * + 100; + const highestPrice = Math.max(...data.trades.map((t) => t.price)); + const lowestPrice = Math.min(...data.trades.map((t) => t.price)); + const priceRange = ((highestPrice - lowestPrice) / lowestPrice) * 100; + + response += `šŸ“Š Summary\n`; + response += `ā€¢ Total Trades: ${data.trades.length}\n`; + response += `ā€¢ Total Volume: ${formatValue(totalVolume)}\n`; + response += `ā€¢ Average Volume: ${formatValue(averageVolume)}\n`; + response += `ā€¢ Buy/Sell Ratio: ${buyRatio.toFixed(1)}% buys\n`; + response += `ā€¢ Average Price: ${formatValue(averagePrice)}\n`; + response += `ā€¢ Price Change: ${priceChange >= 0 ? "+" : ""}${priceChange.toFixed(2)}%\n`; + response += `ā€¢ Price Range: ${priceRange.toFixed(2)}%\n\n`; + + // Add market analysis + const volatility = priceRange / Math.sqrt(data.trades.length); + const volumePerTrade = totalVolume / data.trades.length; + let marketAnalysis = ""; + + if (data.trades.length < 5) { + marketAnalysis = "Insufficient data for detailed analysis"; + } else { + // Analyze trading activity + const activityLevel = + data.trades.length > 20 + ? "high" + : data.trades.length > 10 + ? "moderate" + : "low"; + const volumeLevel = + volumePerTrade > averageVolume * 2 + ? "high" + : volumePerTrade > averageVolume + ? "moderate" + : "low"; + const volatilityLevel = + volatility > 5 ? "high" : volatility > 2 ? "moderate" : "low"; + const trend = + Math.abs(priceChange) < 1 + ? "sideways" + : priceChange > 0 + ? "upward" + : "downward"; + + marketAnalysis = `Market shows ${activityLevel} trading activity with ${volumeLevel} volume per trade. `; + marketAnalysis += `${volatilityLevel.charAt(0).toUpperCase() + volatilityLevel.slice(1)} volatility with a ${trend} price trend.`; + } + + response += `šŸ“ˆ Market Analysis\n`; + response += `ā€¢ ${marketAnalysis}\n\n`; + + response += `šŸ”„ Recent Trades\n`; + data.trades.forEach((trade, index) => { + response += `${index + 1}. ${formatTrade(trade)}\n\n`; + }); + + if (data.totalCount > data.trades.length) { + response += `Showing ${data.trades.length} of ${data.totalCount} total trades.`; + } + + return response; +}; + +export const tokenTradesProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsTokenTradesKeyword(messageText)) { + return null; + } + + const addresses = extractContractAddresses(messageText); + if (addresses.length === 0) { + return null; + } + + const chain = extractChain(messageText); + const limit = extractLimit(messageText); + + elizaLogger.info( + `TOKEN TRADES provider activated for token ${addresses[0]} on ${chain}` + ); + + const tradesData = await getTokenTrades( + apiKey, + addresses[0], + chain, + limit + ); + + if (!tradesData) { + return null; + } + + return formatTokenTradesResponse(tradesData, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/defi/trades-seek-provider.ts b/packages/plugin-birdeye/src/providers/defi/trades-seek-provider.ts new file mode 100644 index 0000000000..8ae96a1600 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/defi/trades-seek-provider.ts @@ -0,0 +1,210 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + extractChain, + extractContractAddresses, + extractLimit, + extractTimeRange, + formatTimestamp, + formatValue, + makeApiRequest, + shortenAddress, +} from "../utils"; + +// Types +interface Trade { + timestamp: number; + price: number; + volume: number; + side: "buy" | "sell"; + source: string; + txHash: string; + buyer?: string; + seller?: string; +} + +interface TradesResponse { + trades: Trade[]; + totalCount: number; + token: string; +} + +// Constants +const TOKEN_TRADE_KEYWORDS = [ + "token trades", + "token trading", + "token transactions", + "token swaps", + "token buys", + "token sells", + "token orders", + "token executions", + "token trade history", + "token trading history", + "token recent trades", + "token market activity", + "token trading activity", + "token market trades", + "token exchange history", +] as const; + +// Helper functions +const containsTokenTradeKeyword = (text: string): boolean => { + return TOKEN_TRADE_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getTradesByTime = async ( + apiKey: string, + contractAddress: string, + timestamp: number, + chain: Chain, + limit: number +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + timestamp: timestamp.toString(), + limit: limit.toString(), + }); + const url = `${BASE_URL}/defi/trades_token_seek_time?${params.toString()}`; + + elizaLogger.info( + `Fetching trades for token ${contractAddress} since ${new Date( + timestamp * 1000 + ).toLocaleString()} on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { apiKey, chain }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching trades by time:", error.message); + } + return null; + } +}; + +const formatTrade = (trade: Trade): string => { + const timestamp = formatTimestamp(trade.timestamp); + const side = trade.side === "buy" ? "šŸŸ¢ Buy" : "šŸ”“ Sell"; + + let response = `${side} - ${timestamp}\n`; + response += `ā€¢ Price: ${formatValue(trade.price)}\n`; + response += `ā€¢ Volume: ${formatValue(trade.volume)}\n`; + response += `ā€¢ Source: ${trade.source}\n`; + if (trade.buyer && trade.seller) { + response += `ā€¢ Buyer: ${shortenAddress(trade.buyer)}\n`; + response += `ā€¢ Seller: ${shortenAddress(trade.seller)}\n`; + } + response += `ā€¢ Tx: ${shortenAddress(trade.txHash)}`; + + return response; +}; + +const formatTradesResponse = ( + data: TradesResponse, + timeRange: { start: number; end: number }, + chain: Chain +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + const startDate = formatTimestamp(timeRange.start); + const endDate = formatTimestamp(timeRange.end); + + let response = `Trade History for ${data.token} on ${chainName}\n`; + response += `Period: ${startDate} to ${endDate}\n\n`; + + if (data.trades.length === 0) { + return response + "No trades found in this time period."; + } + + // Calculate summary statistics + const totalVolume = data.trades.reduce((sum, t) => sum + t.volume, 0); + const averageVolume = totalVolume / data.trades.length; + const buyCount = data.trades.filter((t) => t.side === "buy").length; + const buyRatio = (buyCount / data.trades.length) * 100; + const averagePrice = + data.trades.reduce((sum, t) => sum + t.price, 0) / data.trades.length; + const priceChange = + ((data.trades[data.trades.length - 1].price - data.trades[0].price) / + data.trades[0].price) * + 100; + + response += `šŸ“Š Summary\n`; + response += `ā€¢ Total Trades: ${data.trades.length}\n`; + response += `ā€¢ Total Volume: ${formatValue(totalVolume)}\n`; + response += `ā€¢ Average Volume: ${formatValue(averageVolume)}\n`; + response += `ā€¢ Buy/Sell Ratio: ${buyRatio.toFixed(1)}% buys\n`; + response += `ā€¢ Average Price: ${formatValue(averagePrice)}\n`; + response += `ā€¢ Price Change: ${priceChange >= 0 ? "+" : ""}${priceChange.toFixed(2)}%\n\n`; + + response += `šŸ“ˆ Recent Trades\n`; + data.trades.forEach((trade, index) => { + response += `${index + 1}. ${formatTrade(trade)}\n\n`; + }); + + if (data.totalCount > data.trades.length) { + response += `Showing ${data.trades.length} of ${data.totalCount} total trades.`; + } + + return response; +}; + +export const tokenTradesSeekProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsTokenTradeKeyword(messageText)) { + return null; + } + + const addresses = extractContractAddresses(messageText); + if (addresses.length === 0) { + return null; + } + + const chain = extractChain(messageText); + const timeRange = extractTimeRange(messageText); + const limit = extractLimit(messageText); + + elizaLogger.info( + `TOKEN TRADES SEEK provider activated for token ${addresses[0]} from ${new Date( + timeRange.start * 1000 + ).toLocaleString()} to ${new Date( + timeRange.end * 1000 + ).toLocaleString()} on ${chain}` + ); + + const tradesData = await getTradesByTime( + apiKey, + addresses[0], + timeRange.start, + chain, + limit + ); + + if (!tradesData) { + return null; + } + + return formatTradesResponse(tradesData, timeRange, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/index.ts b/packages/plugin-birdeye/src/providers/index.ts new file mode 100644 index 0000000000..de86d2629c --- /dev/null +++ b/packages/plugin-birdeye/src/providers/index.ts @@ -0,0 +1,148 @@ +import { Provider } from "@elizaos/core"; + +// Import all providers +import { + baseQuoteOHLCVProvider, + networksProvider, + ohlcvProvider, + pairOHLCVProvider, + pairTradesProvider, + pairTradesSeekProvider, + priceHistoryProvider, + priceMultipleProvider, + priceProvider, + priceVolumeProvider, + tokenTradesProvider, + tokenTradesSeekProvider, +} from "./defi"; +import { pairOverviewProvider } from "./pair"; +import { tokenMarketDataProvider } from "./search"; +import { + allMarketListProvider, + newListingProvider, + tokenCreationProvider, + tokenHolderProvider, + tokenListProvider, + tokenMarketProvider, + tokenMetadataProvider, + tokenMintBurnProvider, + tokenOverviewProvider, + tokenSecurityProvider, + tokenTradeProvider, + topTradersProvider, + trendingTokensProvider, +} from "./token"; +import { gainersLosersProvider, tradesSeekProvider } from "./trader"; +import { + portfolioMultichainProvider, + supportedNetworksProvider, + tokenBalanceProvider, + transactionHistoryMultichainProvider, + transactionHistoryProvider, + walletPortfolioProvider, +} from "./wallet"; + +// Export individual providers +export * from "./defi"; +export * from "./pair"; +export * from "./search"; +export * from "./token"; +export * from "./trader"; +export * from "./wallet"; + +// Export providers array +export const providers: Provider[] = [ + // DeFi providers + baseQuoteOHLCVProvider, + networksProvider, + ohlcvProvider, + pairOHLCVProvider, + pairTradesProvider, + pairTradesSeekProvider, + priceHistoryProvider, + priceMultipleProvider, + priceProvider, + priceVolumeProvider, + tokenTradesProvider, + tokenTradesSeekProvider, + + // Pair providers + pairOverviewProvider, + + // Search providers + tokenMarketDataProvider, + + // Token providers + allMarketListProvider, + newListingProvider, + tokenCreationProvider, + tokenHolderProvider, + tokenListProvider, + tokenMarketProvider, + tokenMetadataProvider, + tokenMintBurnProvider, + tokenOverviewProvider, + tokenSecurityProvider, + tokenTradeProvider, + topTradersProvider, + trendingTokensProvider, + + // Trader providers + gainersLosersProvider, + tradesSeekProvider, + + // Wallet providers + portfolioMultichainProvider, + supportedNetworksProvider, + tokenBalanceProvider, + transactionHistoryMultichainProvider, + transactionHistoryProvider, + walletPortfolioProvider, +]; + +// DeFi Providers +export * from "./defi/networks-provider"; +export * from "./defi/ohlcv-base-quote-provider"; +export * from "./defi/ohlcv-pair-provider"; +export * from "./defi/ohlcv-provider"; +export * from "./defi/pair-trades-provider"; +export * from "./defi/pair-trades-seek-provider"; +export * from "./defi/price-history-provider"; +export * from "./defi/price-multiple-provider"; +export * from "./defi/price-provider"; +export * from "./defi/price-volume-provider"; +export * from "./defi/token-trades-provider"; +export * from "./defi/trades-seek-provider"; + +// Token Providers +export * from "./token/all-market-list-provider"; +export * from "./token/new-listing-provider"; +export * from "./token/token-creation-provider"; +export * from "./token/token-holder-provider"; +export * from "./token/token-list-provider"; +export * from "./token/token-market-provider"; +export * from "./token/token-metadata-provider"; +export * from "./token/token-mint-burn-provider"; +export * from "./token/token-overview-provider"; +export * from "./token/token-security-provider"; +export * from "./token/token-trade-provider"; +export * from "./token/top-traders-provider"; +export * from "./token/trending-tokens-provider"; + +// Wallet Providers +export * from "./wallet/portfolio-multichain-provider"; +export * from "./wallet/supported-networks-provider"; +export * from "./wallet/token-balance-provider"; +export * from "./wallet/transaction-history-multichain-provider"; +export * from "./wallet/transaction-history-provider"; +export * from "./wallet/wallet-portfolio-provider"; + +// Trader Providers +export * from "./trader/gainers-losers-provider"; +export * from "./trader/trades-seek-provider"; + +// Pair Providers +export * from "./pair/pair-overview-provider"; + +// Search Providers +export * from "./search/token-market-data-provider"; diff --git a/packages/plugin-birdeye/src/providers/pair/index.ts b/packages/plugin-birdeye/src/providers/pair/index.ts new file mode 100644 index 0000000000..92e68edb47 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/pair/index.ts @@ -0,0 +1 @@ +export * from "./pair-overview-provider"; diff --git a/packages/plugin-birdeye/src/providers/pair/pair-overview-provider.ts b/packages/plugin-birdeye/src/providers/pair/pair-overview-provider.ts new file mode 100644 index 0000000000..f169d59d1c --- /dev/null +++ b/packages/plugin-birdeye/src/providers/pair/pair-overview-provider.ts @@ -0,0 +1,286 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; + +// Types +interface PairToken { + address: string; + symbol: string; + name: string; + decimals: number; + logoURI?: string; +} + +interface PairOverview { + address: string; + baseToken: PairToken; + quoteToken: PairToken; + price: number; + priceChange24h: number; + priceChange24hPercent: number; + volume24h: number; + liquidity: number; + txCount24h: number; + lastTradeUnixTime: number; + dex: string; +} + +interface MultiPairOverview { + [pairAddress: string]: PairOverview; +} + +// Constants +const PAIR_KEYWORDS = [ + "pair", + "pairs", + "trading pair", + "market", + "markets", + "pool", + "pools", + "liquidity pool", + "dex", + "exchange", +] as const; + +const CHAIN_KEYWORDS = [ + "solana", + "ethereum", + "arbitrum", + "avalanche", + "bsc", + "optimism", + "polygon", + "base", + "zksync", + "sui", +] as const; + +const BASE_URL = "https://public-api.birdeye.so"; + +// Helper functions +const containsPairKeyword = (text: string): boolean => { + return PAIR_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const extractChain = (text: string): string => { + const chain = CHAIN_KEYWORDS.find((chain) => + text.toLowerCase().includes(chain.toLowerCase()) + ); + return chain || "solana"; +}; + +const extractPairAddresses = (text: string): string[] => { + const words = text.split(/\s+/); + const addresses: string[] = []; + + for (const word of words) { + // Ethereum-like addresses (0x...) + if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { + addresses.push(word); + } + // Solana addresses (base58, typically 32-44 chars) + if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { + addresses.push(word); + } + } + return addresses; +}; + +const getPairOverview = async ( + apiKey: string, + pairAddress: string, + chain: string = "solana" +): Promise => { + try { + const params = new URLSearchParams({ + address: pairAddress, + }); + const url = `${BASE_URL}/pair/overview_single?${params.toString()}`; + + elizaLogger.info( + `Fetching pair overview for address ${pairAddress} on ${chain} from:`, + url + ); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + }, + }); + + if (!response.ok) { + if (response.status === 404) { + elizaLogger.warn(`Pair not found: ${pairAddress} on ${chain}`); + return null; + } + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.data; + } catch (error) { + elizaLogger.error("Error fetching pair overview:", error); + return null; + } +}; + +const getMultiplePairOverviews = async ( + apiKey: string, + pairAddresses: string[], + chain: string = "solana" +): Promise => { + try { + const params = new URLSearchParams({ + addresses: pairAddresses.join(","), + }); + const url = `${BASE_URL}/pair/overview_multiple?${params.toString()}`; + + elizaLogger.info( + `Fetching multiple pair overviews for ${pairAddresses.length} pairs on ${chain} from:`, + url + ); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.data; + } catch (error) { + elizaLogger.error("Error fetching multiple pair overviews:", error); + return null; + } +}; + +const formatValue = (value: number): string => { + if (value >= 1_000_000_000) { + return `$${(value / 1_000_000_000).toFixed(2)}B`; + } + if (value >= 1_000_000) { + return `$${(value / 1_000_000).toFixed(2)}M`; + } + if (value >= 1_000) { + return `$${(value / 1_000).toFixed(2)}K`; + } + return `$${value.toFixed(2)}`; +}; + +const formatPairOverview = (pair: PairOverview): string => { + const lastTradeTime = new Date( + pair.lastTradeUnixTime * 1000 + ).toLocaleString(); + const priceFormatted = + pair.price < 0.01 ? pair.price.toExponential(2) : pair.price.toFixed(2); + + let response = `${pair.baseToken.symbol}/${pair.quoteToken.symbol} on ${pair.dex}\n`; + response += `ā€¢ Address: ${pair.address}\n`; + response += `ā€¢ Price: $${priceFormatted}\n`; + + const changeSymbol = pair.priceChange24h >= 0 ? "šŸ“ˆ" : "šŸ“‰"; + response += `ā€¢ 24h Change: ${changeSymbol} ${pair.priceChange24hPercent.toFixed(2)}% (${formatValue(pair.priceChange24h)})\n`; + response += `ā€¢ 24h Volume: ${formatValue(pair.volume24h)}\n`; + response += `ā€¢ Liquidity: ${formatValue(pair.liquidity)}\n`; + response += `ā€¢ 24h Transactions: ${pair.txCount24h.toLocaleString()}\n`; + response += `ā€¢ Last Trade: ${lastTradeTime}\n`; + + return response; +}; + +const formatMultiplePairOverviews = ( + pairs: MultiPairOverview, + chain: string +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + let response = `Trading Pairs on ${chainName}:\n\n`; + + if (Object.keys(pairs).length === 0) { + return response + "No pairs found."; + } + + // Sort pairs by liquidity + const sortedPairs = Object.values(pairs).sort( + (a, b) => b.liquidity - a.liquidity + ); + + sortedPairs.forEach((pair, index) => { + response += `${index + 1}. ${formatPairOverview(pair)}\n`; + }); + + return response; +}; + +export const pairOverviewProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsPairKeyword(messageText)) { + return null; + } + + const pairAddresses = extractPairAddresses(messageText); + if (pairAddresses.length === 0) { + return null; + } + + const chain = extractChain(messageText); + + if (pairAddresses.length === 1) { + elizaLogger.info( + `PAIR OVERVIEW provider activated for address ${pairAddresses[0]} on ${chain}` + ); + + const pairData = await getPairOverview( + apiKey, + pairAddresses[0], + chain + ); + + if (!pairData) { + return null; + } + + return formatPairOverview(pairData); + } else { + elizaLogger.info( + `MULTIPLE PAIR OVERVIEW provider activated for ${pairAddresses.length} pairs on ${chain}` + ); + + const pairData = await getMultiplePairOverviews( + apiKey, + pairAddresses, + chain + ); + + if (!pairData) { + return null; + } + + return formatMultiplePairOverviews(pairData, chain); + } + }, +}; diff --git a/packages/plugin-birdeye/src/providers/search/index.ts b/packages/plugin-birdeye/src/providers/search/index.ts new file mode 100644 index 0000000000..a27e735c05 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/search/index.ts @@ -0,0 +1 @@ +export * from "./token-market-data-provider"; diff --git a/packages/plugin-birdeye/src/providers/search/token-market-data-provider.ts b/packages/plugin-birdeye/src/providers/search/token-market-data-provider.ts new file mode 100644 index 0000000000..29690ad1ea --- /dev/null +++ b/packages/plugin-birdeye/src/providers/search/token-market-data-provider.ts @@ -0,0 +1,214 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; + +interface SearchToken { + address: string; + name: string; + symbol: string; + decimals: number; + volume24hUSD: number; + liquidity: number; + logoURI: string; + price: number; +} + +const SEARCH_KEYWORDS = ["search", "find", "look for", "lookup", "locate"]; + +const TOKEN_KEYWORDS = [ + "token", + "tokens", + "coin", + "coins", + "crypto", + "cryptocurrency", + "asset", + "assets", + "sol", + "solana", +]; + +const SUPPORTED_CHAINS = [ + "solana", + "ethereum", + "arbitrum", + "avalanche", + "bsc", + "optimism", + "polygon", + "base", + "zksync", + "sui", +]; + +const BASE_URL = "https://public-api.birdeye.so"; + +interface SearchTokensOptions { + query: string; + chain?: string; + limit?: number; + offset?: number; +} + +const searchTokens = async ( + apiKey: string, + options: SearchTokensOptions +): Promise => { + try { + const { query, chain = "solana", limit = 10, offset = 0 } = options; + + const params = new URLSearchParams({ + query, + limit: limit.toString(), + offset: offset.toString(), + }); + + const url = `${BASE_URL}/defi/v3/search?${params.toString()}`; + elizaLogger.info("Searching tokens from:", url); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.data.tokens || []; + } catch (error) { + elizaLogger.error("Error searching tokens:", error); + throw error; + } +}; + +const formatSearchResultsToString = ( + tokens: SearchToken[], + query: string, + chain: string +): string => { + if (!tokens.length) { + return `No tokens found matching "${query}" on ${chain}.`; + } + + const formattedTokens = tokens + .map((token, index) => { + const priceFormatted = + token.price != null + ? token.price < 0.01 + ? token.price.toExponential(2) + : token.price.toFixed(2) + : "N/A"; + + const volume = + token.volume24hUSD != null + ? `$${(token.volume24hUSD / 1_000_000).toFixed(2)}M` + : "N/A"; + + const liquidity = + token.liquidity != null + ? `$${(token.liquidity / 1_000_000).toFixed(2)}M` + : "N/A"; + + return ( + `${index + 1}. ${token.name} (${token.symbol}):\n` + + ` Address: ${token.address}\n` + + ` Price: $${priceFormatted}\n` + + ` Volume 24h: ${volume}\n` + + ` Liquidity: ${liquidity}` + ); + }) + .join("\n\n"); + + return `Search results for "${query}" on ${chain.charAt(0).toUpperCase() + chain.slice(1)}:\n\n${formattedTokens}`; +}; + +export const tokenMarketDataProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + return null; + } + + const messageText = message.content.text.toLowerCase(); + + // Check if message contains search-related keywords + const hasSearchKeyword = SEARCH_KEYWORDS.some((keyword) => + messageText.includes(keyword) + ); + + // Check if message contains token-related keywords + const hasTokenKeyword = TOKEN_KEYWORDS.some((keyword) => + messageText.includes(keyword) + ); + + // Extract potential search query + // Look for quotes first + let searchQuery = + messageText.match(/"([^"]+)"/)?.[1] || + messageText.match(/'([^']+)'/)?.[1]; + + // If no quotes, try to extract query after search keywords + if (!searchQuery) { + for (const keyword of SEARCH_KEYWORDS) { + if (messageText.includes(keyword)) { + const parts = messageText.split(keyword); + if (parts[1]) { + searchQuery = parts[1] + .trim() + .split(/[\s,.]/) + .filter((word) => word.length > 1) + .join(" ") + .trim(); + break; + } + } + } + } + + // Determine which chain is being asked about + const requestedChain = + SUPPORTED_CHAINS.find((chain) => + messageText.includes(chain.toLowerCase()) + ) || "solana"; + + // Get the current offset from state or default to 0 + const currentOffset = (_state?.searchTokensOffset as number) || 0; + + // Combine signals to make decision + const shouldProvideData = + searchQuery && hasSearchKeyword && hasTokenKeyword; + + if (!shouldProvideData || !searchQuery) { + return null; + } + + elizaLogger.info( + `Search tokens provider activated for query "${searchQuery}" on ${requestedChain}` + ); + + const searchResults = await searchTokens(apiKey, { + query: searchQuery, + chain: requestedChain, + offset: currentOffset, + limit: 10, + }); + + return formatSearchResultsToString( + searchResults, + searchQuery, + requestedChain + ); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/token/__tests__/token-overview-provider.test.ts b/packages/plugin-birdeye/src/providers/token/__tests__/token-overview-provider.test.ts new file mode 100644 index 0000000000..85271a9fce --- /dev/null +++ b/packages/plugin-birdeye/src/providers/token/__tests__/token-overview-provider.test.ts @@ -0,0 +1,189 @@ +import { IAgentRuntime, Memory, State } from "@elizaos/core"; +import { tokenOverviewProvider } from "../token-overview-provider"; + +// Mock data +const mockTokenOverview = { + address: "0x1234567890123456789012345678901234567890", + symbol: "TEST", + name: "Test Token", + decimals: 18, + logoURI: "https://example.com/logo.png", + price: 1.23, + priceChange24hPercent: 5.67, + liquidity: 1000000, + marketCap: 10000000, + realMc: 9000000, + supply: 1000000, + circulatingSupply: 900000, + holder: 1000, + v24h: 100000, + v24hUSD: 123000, + lastTradeUnixTime: 1704067200, + numberMarkets: 5, + extensions: { + website: "https://example.com", + twitter: "https://twitter.com/test", + telegram: "https://t.me/test", + discord: "https://discord.gg/test", + description: "A test token", + coingeckoId: "test-token", + }, +}; + +// Mock fetch globally +global.fetch = jest.fn(); + +describe("Token Overview Provider", () => { + let mockRuntime: IAgentRuntime; + let mockMessage: Memory; + let mockState: State; + + beforeEach(() => { + // Reset mocks + jest.clearAllMocks(); + + // Mock runtime + mockRuntime = { + getSetting: jest.fn().mockReturnValue("mock-api-key"), + } as unknown as IAgentRuntime; + + // Mock message + mockMessage = { + content: { + text: "Show me overview of 0x1234567890123456789012345678901234567890 on ethereum", + }, + } as Memory; + + // Mock state + mockState = {} as State; + + // Mock successful fetch response + (global.fetch as jest.Mock).mockResolvedValue({ + ok: true, + json: async () => ({ data: mockTokenOverview }), + }); + }); + + test("returns null when API key is missing", async () => { + (mockRuntime.getSetting as jest.Mock).mockReturnValue(null); + const result = await tokenOverviewProvider.get( + mockRuntime, + mockMessage, + mockState + ); + expect(result).toBeNull(); + }); + + test("returns null when message does not contain overview keywords", async () => { + mockMessage.content.text = "random message without overview keywords"; + const result = await tokenOverviewProvider.get( + mockRuntime, + mockMessage, + mockState + ); + expect(result).toBeNull(); + }); + + test("returns null when no contract address is found", async () => { + mockMessage.content.text = "show overview of invalid-address"; + const result = await tokenOverviewProvider.get( + mockRuntime, + mockMessage, + mockState + ); + expect(result).toBeNull(); + }); + + test("handles API error gracefully", async () => { + (global.fetch as jest.Mock).mockRejectedValue(new Error("API Error")); + const result = await tokenOverviewProvider.get( + mockRuntime, + mockMessage, + mockState + ); + expect(result).toBeNull(); + }); + + test("handles 404 response gracefully", async () => { + (global.fetch as jest.Mock).mockResolvedValue({ + ok: false, + status: 404, + }); + const result = await tokenOverviewProvider.get( + mockRuntime, + mockMessage, + mockState + ); + expect(result).toBeNull(); + }); + + test("formats token overview correctly", async () => { + const result = await tokenOverviewProvider.get( + mockRuntime, + mockMessage, + mockState + ); + + // Verify the result contains all expected sections + expect(result).toContain("Token Overview for Test Token (TEST)"); + expect(result).toContain("šŸ“Š Market Data"); + expect(result).toContain("šŸ“ˆ Trading Info"); + expect(result).toContain("šŸ’° Supply Information"); + expect(result).toContain("šŸ”— Token Details"); + expect(result).toContain("šŸŒ Social Links"); + + // Verify specific data points + expect(result).toContain(`Current Price: $${mockTokenOverview.price}`); + expect(result).toContain(`Market Cap: $${mockTokenOverview.marketCap}`); + expect(result).toContain(mockTokenOverview.address); + expect(result).toContain(mockTokenOverview.extensions.website); + }); + + test("handles missing social links gracefully", async () => { + const tokenWithoutSocials = { + ...mockTokenOverview, + extensions: undefined, + }; + (global.fetch as jest.Mock).mockResolvedValue({ + ok: true, + json: async () => ({ data: tokenWithoutSocials }), + }); + + const result = await tokenOverviewProvider.get( + mockRuntime, + mockMessage, + mockState + ); + expect(result).not.toContain("šŸŒ Social Links"); + }); + + test("extracts chain correctly", async () => { + mockMessage.content.text = + "show overview of 0x1234567890123456789012345678901234567890 on ethereum"; + await tokenOverviewProvider.get(mockRuntime, mockMessage, mockState); + + expect(global.fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + "x-chain": "ethereum", + }), + }) + ); + }); + + test("defaults to solana chain when not specified", async () => { + mockMessage.content.text = + "show overview of 0x1234567890123456789012345678901234567890"; + await tokenOverviewProvider.get(mockRuntime, mockMessage, mockState); + + expect(global.fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + "x-chain": "solana", + }), + }) + ); + }); +}); diff --git a/packages/plugin-birdeye/src/providers/token/all-market-list-provider.ts b/packages/plugin-birdeye/src/providers/token/all-market-list-provider.ts new file mode 100644 index 0000000000..dd7d9d8e93 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/token/all-market-list-provider.ts @@ -0,0 +1,114 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { BASE_URL, Chain, makeApiRequest } from "../utils"; + +// Types +interface Market { + address: string; + name: string; + symbol: string; + baseToken: string; + quoteToken: string; + volume24h: number; + tvl: number; + lastTradeTime: number; +} + +interface AllMarketsResponse { + markets: Market[]; +} + +// Constants +const MARKET_LIST_KEYWORDS = [ + "all markets", + "market list", + "trading pairs", + "available markets", + "list markets", + "show markets", +] as const; + +// Helper functions +const containsMarketListKeyword = (text: string): boolean => { + return MARKET_LIST_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getAllMarkets = async ( + apiKey: string, + chain: Chain = "solana" +): Promise => { + try { + const url = `${BASE_URL}/token/all_market_list`; + + elizaLogger.info("Fetching all markets from:", url); + + return await makeApiRequest(url, { apiKey, chain }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching markets:", error.message); + } + return null; + } +}; + +const formatAllMarketsResponse = (data: AllMarketsResponse): string => { + let response = "šŸ“Š Available Markets\n\n"; + + // Sort markets by volume + const sortedMarkets = [...data.markets].sort( + (a, b) => b.volume24h - a.volume24h + ); + + sortedMarkets.forEach((market) => { + const lastTradeDate = new Date( + market.lastTradeTime * 1000 + ).toLocaleString(); + + response += `${market.name} (${market.symbol})\n`; + response += `ā€¢ Address: ${market.address}\n`; + response += `ā€¢ Base Token: ${market.baseToken}\n`; + response += `ā€¢ Quote Token: ${market.quoteToken}\n`; + response += `ā€¢ 24h Volume: $${market.volume24h.toLocaleString()}\n`; + response += `ā€¢ TVL: $${market.tvl.toLocaleString()}\n`; + response += `ā€¢ Last Trade: ${lastTradeDate}\n\n`; + }); + + return response.trim(); +}; + +export const allMarketListProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsMarketListKeyword(messageText)) { + return null; + } + + elizaLogger.info("ALL_MARKET_LIST provider activated"); + + const marketsData = await getAllMarkets(apiKey); + + if (!marketsData) { + return null; + } + + return formatAllMarketsResponse(marketsData); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/token/index.ts b/packages/plugin-birdeye/src/providers/token/index.ts new file mode 100644 index 0000000000..1fdabaf640 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/token/index.ts @@ -0,0 +1,13 @@ +export * from "./all-market-list-provider"; +export * from "./new-listing-provider"; +export * from "./token-creation-provider"; +export * from "./token-holder-provider"; +export * from "./token-list-provider"; +export * from "./token-market-provider"; +export * from "./token-metadata-provider"; +export * from "./token-mint-burn-provider"; +export * from "./token-overview-provider"; +export * from "./token-security-provider"; +export * from "./token-trade-provider"; +export * from "./top-traders-provider"; +export * from "./trending-tokens-provider"; diff --git a/packages/plugin-birdeye/src/providers/token/new-listing-provider.ts b/packages/plugin-birdeye/src/providers/token/new-listing-provider.ts new file mode 100644 index 0000000000..5ee8952577 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/token/new-listing-provider.ts @@ -0,0 +1,113 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { BASE_URL, Chain, makeApiRequest } from "../utils"; + +// Types +interface TokenListing { + address: string; + name: string; + symbol: string; + listingTime: number; + initialPrice: number; + currentPrice: number; + priceChange: number; + volume24h: number; +} + +interface NewListingsResponse { + listings: TokenListing[]; +} + +// Constants +const NEW_LISTING_KEYWORDS = [ + "new listings", + "newly listed", + "recent listings", + "latest tokens", + "new tokens", +] as const; + +// Helper functions +const containsNewListingKeyword = (text: string): boolean => { + return NEW_LISTING_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getNewListings = async ( + apiKey: string, + chain: Chain = "solana" +): Promise => { + try { + const url = `${BASE_URL}/token/new_listing`; + + elizaLogger.info("Fetching new token listings from:", url); + + return await makeApiRequest(url, { + apiKey, + chain, + }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching new listings:", error.message); + } + return null; + } +}; + +const formatNewListingsResponse = (data: NewListingsResponse): string => { + let response = "šŸ†• New Token Listings\n\n"; + + data.listings.forEach((listing) => { + const listingDate = new Date( + listing.listingTime * 1000 + ).toLocaleString(); + const priceChangePercent = (listing.priceChange * 100).toFixed(2); + const priceChangeEmoji = listing.priceChange >= 0 ? "šŸ“ˆ" : "šŸ“‰"; + + response += `${listing.name} (${listing.symbol}) ${priceChangeEmoji}\n`; + response += `ā€¢ Address: ${listing.address}\n`; + response += `ā€¢ Listed: ${listingDate}\n`; + response += `ā€¢ Initial Price: $${listing.initialPrice.toFixed(6)}\n`; + response += `ā€¢ Current Price: $${listing.currentPrice.toFixed(6)}\n`; + response += `ā€¢ Price Change: ${priceChangePercent}%\n`; + response += `ā€¢ 24h Volume: $${listing.volume24h.toLocaleString()}\n\n`; + }); + + return response.trim(); +}; + +export const newListingProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsNewListingKeyword(messageText)) { + return null; + } + + elizaLogger.info("NEW_LISTING provider activated"); + + const listingsData = await getNewListings(apiKey); + + if (!listingsData) { + return null; + } + + return formatNewListingsResponse(listingsData); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/token/token-creation-provider.ts b/packages/plugin-birdeye/src/providers/token/token-creation-provider.ts new file mode 100644 index 0000000000..fc78c81f39 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/token/token-creation-provider.ts @@ -0,0 +1,199 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + extractChain, + extractContractAddresses, + formatTimestamp, + formatValue, + makeApiRequest, + shortenAddress, +} from "../utils"; + +// Types +interface CreationData { + creator: string; + creatorBalance: number; + creatorBalanceUSD: number; + creatorShare: number; + creationTime: number; + initialSupply: number; + initialSupplyUSD: number; + creationTx: string; +} + +interface CreationResponse { + data: CreationData; + token: string; +} + +// Constants +const CREATION_KEYWORDS = [ + "creation", + "creator", + "created", + "launch", + "launched", + "deployment", + "deployed", + "initial supply", + "token creation", + "token launch", + "token deployment", + "token origin", + "token history", + "token birth", + "genesis", +] as const; + +// Helper functions +const containsCreationKeyword = (text: string): boolean => { + return CREATION_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getTokenCreation = async ( + apiKey: string, + contractAddress: string, + chain: Chain +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + }); + const url = `${BASE_URL}/token/creation?${params.toString()}`; + + elizaLogger.info( + `Fetching creation data for ${contractAddress} on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { apiKey, chain }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching creation data:", error.message); + } + return null; + } +}; + +const analyzeCreationMetrics = (data: CreationData): string => { + let analysis = ""; + + // Analyze creator's share + if (data.creatorShare > 50) { + analysis += + "āš ļø Creator holds majority of supply, high concentration risk. "; + } else if (data.creatorShare > 20) { + analysis += "āš” Creator maintains significant holdings. "; + } else if (data.creatorShare > 5) { + analysis += "āœ… Creator retains moderate holdings. "; + } else { + analysis += "šŸ”„ Creator holds minimal share of supply. "; + } + + // Analyze initial supply value + if (data.initialSupplyUSD > 1000000) { + analysis += + "šŸ’° Large initial supply value indicates significant launch. "; + } else if (data.initialSupplyUSD > 100000) { + analysis += + "šŸ’« Moderate initial supply value suggests standard launch. "; + } else { + analysis += + "šŸŒ± Small initial supply value indicates grassroots launch. "; + } + + // Analyze creator's current position + const valueChange = data.creatorBalanceUSD / data.initialSupplyUSD; + if (valueChange > 1.5) { + analysis += "šŸ“ˆ Creator's position has significantly appreciated. "; + } else if (valueChange < 0.5) { + analysis += "šŸ“‰ Creator's position has notably decreased. "; + } + + return analysis; +}; + +const formatCreationResponse = ( + data: CreationResponse, + chain: Chain +): string => { + const { data: creationData } = data; + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + + let response = `Token Creation Data for ${data.token} on ${chainName}\n\n`; + + // Creation Analysis + response += "šŸ“Š Creation Analysis\n"; + response += analyzeCreationMetrics(creationData) + "\n\n"; + + // Creation Details + response += "šŸŽ‚ Creation Details\n"; + response += `Creation Time: ${formatTimestamp(creationData.creationTime)}\n`; + response += `Creator: ${shortenAddress(creationData.creator)}\n`; + response += `Creation Tx: ${shortenAddress(creationData.creationTx)}\n\n`; + + // Supply Information + response += "šŸ’° Supply Information\n"; + response += `Initial Supply: ${formatValue(creationData.initialSupply)}\n`; + response += `Initial Value: ${formatValue(creationData.initialSupplyUSD)}\n\n`; + + // Creator Holdings + response += "šŸ‘¤ Creator Holdings\n"; + response += `Current Balance: ${formatValue(creationData.creatorBalance)}\n`; + response += `Current Value: ${formatValue(creationData.creatorBalanceUSD)}\n`; + response += `Share of Supply: ${creationData.creatorShare.toFixed(2)}%`; + + return response; +}; + +export const tokenCreationProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsCreationKeyword(messageText)) { + return null; + } + + const addresses = extractContractAddresses(messageText); + if (addresses.length === 0) { + return null; + } + + const chain = extractChain(messageText); + + elizaLogger.info( + `TOKEN CREATION provider activated for ${addresses[0]} on ${chain}` + ); + + const creationData = await getTokenCreation( + apiKey, + addresses[0], + chain + ); + + if (!creationData) { + return null; + } + + return formatCreationResponse(creationData, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/token/token-holder-provider.ts b/packages/plugin-birdeye/src/providers/token/token-holder-provider.ts new file mode 100644 index 0000000000..5f9c40b369 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/token/token-holder-provider.ts @@ -0,0 +1,220 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + extractChain, + extractContractAddresses, + extractLimit, + formatValue, + makeApiRequest, + shortenAddress, +} from "../utils"; + +// Types +interface HolderData { + address: string; + balance: number; + balanceUSD: number; + share: number; + rank: number; +} + +interface TokenHolderResponse { + holders: HolderData[]; + totalCount: number; + token: string; +} + +// Constants +const HOLDER_KEYWORDS = [ + "holders", + "holding", + "token holders", + "token holding", + "who holds", + "who owns", + "ownership", + "distribution", + "token distribution", + "token ownership", + "top holders", + "largest holders", + "biggest holders", + "whale holders", + "whale watching", +] as const; + +// Helper functions +const containsHolderKeyword = (text: string): boolean => { + return HOLDER_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getTokenHolders = async ( + apiKey: string, + contractAddress: string, + chain: Chain, + limit: number +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + limit: limit.toString(), + }); + const url = `${BASE_URL}/token/holder?${params.toString()}`; + + elizaLogger.info( + `Fetching token holders for ${contractAddress} on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { + apiKey, + chain, + }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching token holders:", error.message); + } + return null; + } +}; + +const formatHolderData = (holder: HolderData): string => { + let response = `${holder.rank}. ${shortenAddress(holder.address)}\n`; + response += ` ā€¢ Balance: ${holder.balance ? formatValue(holder.balance) : "N/A"}\n`; + response += ` ā€¢ Value: ${holder.balanceUSD ? formatValue(holder.balanceUSD) : "N/A"}\n`; + response += ` ā€¢ Share: ${holder.share ? holder.share.toFixed(2) : "0.00"}%`; + return response; +}; + +const analyzeDistribution = (holders: HolderData[]): string => { + // Calculate concentration metrics + const top10Share = holders + .slice(0, 10) + .reduce((sum, h) => sum + h.share, 0); + const top20Share = holders + .slice(0, 20) + .reduce((sum, h) => sum + h.share, 0); + const top50Share = holders + .slice(0, 50) + .reduce((sum, h) => sum + h.share, 0); + + let analysis = ""; + + // Analyze top holder concentration + const topHolder = holders[0]; + if (topHolder.share > 50) { + analysis += + "šŸšØ Extremely high concentration: Top holder owns majority of supply. "; + } else if (topHolder.share > 20) { + analysis += + "āš ļø High concentration: Top holder owns significant portion. "; + } else if (topHolder.share > 10) { + analysis += + "ā„¹ļø Moderate concentration: Top holder owns notable portion. "; + } else { + analysis += + "āœ… Good distribution: No single holder owns dominant share. "; + } + + // Analyze overall distribution + if (top10Share > 80) { + analysis += + "Top 10 holders control vast majority of supply, indicating high centralization. "; + } else if (top10Share > 50) { + analysis += + "Top 10 holders control majority of supply, showing moderate centralization. "; + } else { + analysis += + "Top 10 holders control less than half of supply, suggesting good distribution. "; + } + + // Provide distribution metrics + analysis += `\n\nDistribution Metrics:\n`; + analysis += `ā€¢ Top 10 Holders: ${top10Share.toFixed(2)}%\n`; + analysis += `ā€¢ Top 20 Holders: ${top20Share.toFixed(2)}%\n`; + analysis += `ā€¢ Top 50 Holders: ${top50Share.toFixed(2)}%`; + + return analysis; +}; + +const formatHolderResponse = ( + data: TokenHolderResponse, + chain: Chain +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + + let response = `Token Holders for ${data.token} on ${chainName}\n\n`; + + if (data.holders.length === 0) { + return response + "No holder data found."; + } + + response += `šŸ“Š Distribution Analysis\n`; + response += analyzeDistribution(data.holders); + response += "\n\n"; + + response += `šŸ‘„ Top Holders\n`; + data.holders.forEach((holder) => { + response += formatHolderData(holder) + "\n\n"; + }); + + if (data.totalCount > data.holders.length) { + response += `Showing ${data.holders.length} of ${data.totalCount} total holders.`; + } + + return response; +}; + +export const tokenHolderProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsHolderKeyword(messageText)) { + return null; + } + + const addresses = extractContractAddresses(messageText); + if (addresses.length === 0) { + return null; + } + + const chain = extractChain(messageText); + const limit = extractLimit(messageText); + + elizaLogger.info( + `TOKEN HOLDER provider activated for ${addresses[0]} on ${chain}` + ); + + const holderData = await getTokenHolders( + apiKey, + addresses[0], + chain, + limit + ); + + if (!holderData) { + return null; + } + + return formatHolderResponse(holderData, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/token/token-list-provider.ts b/packages/plugin-birdeye/src/providers/token/token-list-provider.ts new file mode 100644 index 0000000000..dfe95705b2 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/token/token-list-provider.ts @@ -0,0 +1,198 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + extractChain, + formatPercentChange, + formatValue, + makeApiRequest, + shortenAddress, +} from "../utils"; + +// Types +interface TokenListData { + address: string; + name: string; + symbol: string; + decimals: number; + price: number; + priceChange24h: number; + volume24h: number; + marketCap: number; + liquidity: number; + rank: number; +} + +interface TokenListResponse { + data: TokenListData[]; + totalCount: number; +} + +// Constants +const LIST_KEYWORDS = [ + "list", + "top tokens", + "popular tokens", + "trending tokens", + "token list", + "token ranking", + "token rankings", + "token leaderboard", + "best tokens", + "highest volume", + "highest market cap", + "highest liquidity", +] as const; + +// Helper functions +const containsListKeyword = (text: string): boolean => { + return LIST_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getTokenList = async ( + apiKey: string, + chain: Chain, + limit: number = 10 +): Promise => { + try { + const params = new URLSearchParams({ + limit: limit.toString(), + }); + const url = `${BASE_URL}/token/list?${params.toString()}`; + + elizaLogger.info(`Fetching token list on ${chain} from:`, url); + + return await makeApiRequest(url, { apiKey, chain }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching token list:", error.message); + } + return null; + } +}; + +const formatTokenData = (token: TokenListData, rank: number): string => { + let response = `${rank}. ${token.name} (${token.symbol})\n`; + response += ` ā€¢ Address: ${shortenAddress(token.address)}\n`; + response += ` ā€¢ Price: ${formatValue(token.price)} (${formatPercentChange(token.priceChange24h)})\n`; + response += ` ā€¢ Volume 24h: ${formatValue(token.volume24h)}\n`; + response += ` ā€¢ Market Cap: ${formatValue(token.marketCap)}\n`; + response += ` ā€¢ Liquidity: ${formatValue(token.liquidity)}`; + return response; +}; + +const analyzeTokenList = (tokens: TokenListData[]): string => { + let analysis = ""; + + // Volume analysis + const validVolumes = tokens.filter((t) => t.volume24h != null); + const totalVolume = validVolumes.reduce((sum, t) => sum + t.volume24h, 0); + const avgVolume = + validVolumes.length > 0 ? totalVolume / validVolumes.length : 0; + const highVolumeTokens = validVolumes.filter( + (t) => t.volume24h > avgVolume * 2 + ); + + if (highVolumeTokens.length > 0) { + analysis += `šŸ”„ ${highVolumeTokens.length} tokens showing exceptional trading activity.\n`; + } + + // Price movement analysis + const validPriceChanges = tokens.filter((t) => t.priceChange24h != null); + const positiveMovers = validPriceChanges.filter( + (t) => t.priceChange24h > 0 + ); + const strongMovers = validPriceChanges.filter( + (t) => Math.abs(t.priceChange24h) > 10 + ); + + if (validPriceChanges.length > 0) { + if (positiveMovers.length > validPriceChanges.length / 2) { + analysis += + "šŸ“ˆ Market showing bullish trend with majority positive price movement.\n"; + } else { + analysis += + "šŸ“‰ Market showing bearish trend with majority negative price movement.\n"; + } + + if (strongMovers.length > 0) { + analysis += `āš” ${strongMovers.length} tokens with significant price movement (>10%).\n`; + } + } + + // Liquidity analysis + const totalLiquidity = tokens.reduce((sum, t) => sum + t.liquidity, 0); + const avgLiquidity = totalLiquidity / tokens.length; + const highLiquidityTokens = tokens.filter( + (t) => t.liquidity > avgLiquidity * 2 + ); + + if (highLiquidityTokens.length > 0) { + analysis += `šŸ’§ ${highLiquidityTokens.length} tokens with notably high liquidity.\n`; + } + + return analysis; +}; + +const formatListResponse = (data: TokenListResponse, chain: Chain): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + + let response = `Top Tokens on ${chainName}\n\n`; + + // Market Analysis + response += "šŸ“Š Market Analysis\n"; + response += analyzeTokenList(data.data) + "\n\n"; + + // Token List + response += "šŸ† Token Rankings\n"; + data.data.forEach((token, index) => { + response += formatTokenData(token, index + 1) + "\n\n"; + }); + + if (data.totalCount > data.data.length) { + response += `Showing ${data.data.length} of ${data.totalCount} total tokens.`; + } + + return response; +}; + +export const tokenListProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsListKeyword(messageText)) { + return null; + } + + const chain = extractChain(messageText); + const limit = messageText.toLowerCase().includes("all") ? 100 : 10; + + elizaLogger.info(`TOKEN LIST provider activated for ${chain}`); + + const listData = await getTokenList(apiKey, chain, limit); + + if (!listData) { + return null; + } + + return formatListResponse(listData, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/token/token-market-provider.ts b/packages/plugin-birdeye/src/providers/token/token-market-provider.ts new file mode 100644 index 0000000000..a5f2ccb85f --- /dev/null +++ b/packages/plugin-birdeye/src/providers/token/token-market-provider.ts @@ -0,0 +1,217 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + extractChain, + extractContractAddresses, + formatPercentChange, + formatValue, + makeApiRequest, +} from "../utils"; + +// Types +interface MarketData { + price: number; + priceChange24h: number; + priceChange7d: number; + priceChange14d: number; + priceChange30d: number; + volume24h: number; + volume7d: number; + marketCap: number; + fullyDilutedValuation: number; + rank: number; + liquidity: number; + liquidityChange24h: number; + liquidityChange7d: number; +} + +interface MarketResponse { + data: MarketData; + token: string; +} + +// Constants +const MARKET_KEYWORDS = [ + "market", + "price", + "volume", + "liquidity", + "market cap", + "mcap", + "fdv", + "valuation", + "market data", + "market info", + "market stats", + "market metrics", + "market overview", + "market analysis", + "price change", + "price movement", + "price action", + "price performance", +] as const; + +// Helper functions +const containsMarketKeyword = (text: string): boolean => { + return MARKET_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getTokenMarket = async ( + apiKey: string, + contractAddress: string, + chain: Chain +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + }); + const url = `${BASE_URL}/token/market?${params.toString()}`; + + elizaLogger.info( + `Fetching market data for ${contractAddress} on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { apiKey, chain }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching market data:", error.message); + } + return null; + } +}; + +const formatPriceChanges = (data: MarketData): string => { + let changes = ""; + changes += `24h: ${formatPercentChange(data.priceChange24h)}\n`; + changes += `7d: ${formatPercentChange(data.priceChange7d)}\n`; + changes += `14d: ${formatPercentChange(data.priceChange14d)}\n`; + changes += `30d: ${formatPercentChange(data.priceChange30d)}`; + return changes; +}; + +const formatLiquidityChanges = (data: MarketData): string => { + let changes = ""; + changes += `24h: ${formatPercentChange(data.liquidityChange24h)}\n`; + changes += `7d: ${formatPercentChange(data.liquidityChange7d)}`; + return changes; +}; + +const analyzeMarketMetrics = (data: MarketData): string => { + let analysis = ""; + + // Price trend analysis + if (data.priceChange24h > 5) { + analysis += "šŸ“ˆ Strong bullish momentum in the last 24 hours. "; + } else if (data.priceChange24h < -5) { + analysis += "šŸ“‰ Significant price decline in the last 24 hours. "; + } + + // Volume analysis + const volumeToMcap = (data.volume24h / data.marketCap) * 100; + if (volumeToMcap > 20) { + analysis += "šŸ”„ High trading activity relative to market cap. "; + } else if (volumeToMcap < 1) { + analysis += "āš ļø Low trading volume relative to market cap. "; + } + + // Liquidity analysis + const liquidityToMcap = (data.liquidity / data.marketCap) * 100; + if (liquidityToMcap > 30) { + analysis += "šŸ’§ Strong liquidity relative to market cap. "; + } else if (liquidityToMcap < 5) { + analysis += "āš ļø Limited liquidity relative to market cap. "; + } + + // Market cap vs FDV analysis + if (data.fullyDilutedValuation > data.marketCap * 3) { + analysis += "āš ļø High potential for dilution based on FDV. "; + } + + return analysis || "Market metrics are within normal ranges."; +}; + +const formatMarketResponse = (data: MarketResponse, chain: Chain): string => { + const { data: marketData } = data; + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + + let response = `Market Data for ${data.token} on ${chainName}\n\n`; + + // Market Analysis + response += "šŸ“Š Market Analysis\n"; + response += analyzeMarketMetrics(marketData) + "\n\n"; + + // Price Information + response += "šŸ’° Price Information\n"; + response += `Current Price: ${formatValue(marketData.price)}\n\n`; + response += "Price Changes:\n"; + response += formatPriceChanges(marketData) + "\n\n"; + + // Volume Information + response += "šŸ“ˆ Volume Information\n"; + response += `24h Volume: ${marketData.volume24h ? formatValue(marketData.volume24h) : "N/A"}\n`; + response += `7d Volume: ${marketData.volume7d ? formatValue(marketData.volume7d) : "N/A"}\n\n`; + + // Market Metrics + response += "šŸ“Š Market Metrics\n"; + response += `Market Cap: ${marketData.marketCap ? formatValue(marketData.marketCap) : "N/A"}\n`; + response += `Fully Diluted Valuation: ${marketData.fullyDilutedValuation ? formatValue(marketData.fullyDilutedValuation) : "N/A"}\n`; + response += `Market Rank: ${marketData.rank ? `#${marketData.rank}` : "N/A"}\n\n`; + + // Liquidity Information + response += "šŸ’§ Liquidity Information\n"; + response += `Current Liquidity: ${marketData.liquidity ? formatValue(marketData.liquidity) : "N/A"}\n\n`; + response += "Liquidity Changes:\n"; + response += formatLiquidityChanges(marketData); + + return response; +}; + +export const tokenMarketProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsMarketKeyword(messageText)) { + return null; + } + + const addresses = extractContractAddresses(messageText); + if (addresses.length === 0) { + return null; + } + + const chain = extractChain(messageText); + + elizaLogger.info( + `TOKEN MARKET provider activated for ${addresses[0]} on ${chain}` + ); + + const marketData = await getTokenMarket(apiKey, addresses[0], chain); + + if (!marketData) { + return null; + } + + return formatMarketResponse(marketData, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/token/token-metadata-provider.ts b/packages/plugin-birdeye/src/providers/token/token-metadata-provider.ts new file mode 100644 index 0000000000..590b70327c --- /dev/null +++ b/packages/plugin-birdeye/src/providers/token/token-metadata-provider.ts @@ -0,0 +1,197 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + extractChain, + extractContractAddresses, + formatValue, + makeApiRequest, +} from "../utils"; + +// Types +interface TokenMetadata { + address: string; + name: string; + symbol: string; + decimals: number; + totalSupply: number; + totalSupplyUSD: number; + website: string; + twitter: string; + telegram: string; + discord: string; + coingeckoId: string; + description: string; + logo: string; + tags: string[]; +} + +interface MetadataResponse { + metadata: TokenMetadata; +} + +// Constants +const METADATA_KEYWORDS = [ + "metadata", + "token info", + "token information", + "token details", + "token data", + "token description", + "token profile", + "token overview", + "token stats", + "token statistics", + "token social", + "token links", + "token website", + "token socials", +] as const; + +// Helper functions +const containsMetadataKeyword = (text: string): boolean => { + return METADATA_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getTokenMetadata = async ( + apiKey: string, + contractAddress: string, + chain: Chain +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + }); + const url = `${BASE_URL}/token/metadata?${params.toString()}`; + + elizaLogger.info( + `Fetching token metadata for ${contractAddress} on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { apiKey, chain }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching token metadata:", error.message); + } + return null; + } +}; + +const formatSocialLinks = (metadata: TokenMetadata): string => { + const links = []; + + if (metadata.website) { + links.push(`šŸŒ [Website](${metadata.website})`); + } + if (metadata.twitter) { + links.push(`šŸ¦ [Twitter](${metadata.twitter})`); + } + if (metadata.telegram) { + links.push(`šŸ“± [Telegram](${metadata.telegram})`); + } + if (metadata.discord) { + links.push(`šŸ’¬ [Discord](${metadata.discord})`); + } + if (metadata.coingeckoId) { + links.push( + `šŸ¦Ž [CoinGecko](https://www.coingecko.com/en/coins/${metadata.coingeckoId})` + ); + } + + return links.length > 0 ? links.join("\n") : "No social links available"; +}; + +const formatMetadataResponse = ( + data: MetadataResponse, + chain: Chain +): string => { + const { metadata } = data; + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + + let response = `Token Metadata for ${metadata.name} (${metadata.symbol}) on ${chainName}\n\n`; + + // Basic Information + response += "šŸ“ Basic Information\n"; + response += `ā€¢ Name: ${metadata.name}\n`; + response += `ā€¢ Symbol: ${metadata.symbol}\n`; + response += `ā€¢ Address: ${metadata.address}\n`; + response += `ā€¢ Decimals: ${metadata.decimals}\n`; + response += `ā€¢ Total Supply: ${formatValue(metadata.totalSupply)}\n`; + response += `ā€¢ Total Supply USD: ${formatValue(metadata.totalSupplyUSD)}\n`; + + // Description + if (metadata.description) { + response += "\nšŸ“‹ Description\n"; + response += metadata.description + "\n"; + } + + // Tags + if (metadata.tags && metadata.tags.length > 0) { + response += "\nšŸ·ļø Tags\n"; + response += metadata.tags.map((tag) => `#${tag}`).join(" ") + "\n"; + } + + // Social Links + response += "\nšŸ”— Social Links\n"; + response += formatSocialLinks(metadata) + "\n"; + + // Logo + if (metadata.logo) { + response += "\nšŸ–¼ļø Logo\n"; + response += metadata.logo; + } + + return response; +}; + +export const tokenMetadataProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsMetadataKeyword(messageText)) { + return null; + } + + const addresses = extractContractAddresses(messageText); + if (addresses.length === 0) { + return null; + } + + const chain = extractChain(messageText); + + elizaLogger.info( + `TOKEN METADATA provider activated for ${addresses[0]} on ${chain}` + ); + + const metadataData = await getTokenMetadata( + apiKey, + addresses[0], + chain + ); + + if (!metadataData) { + return null; + } + + return formatMetadataResponse(metadataData, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/token/token-mint-burn-provider.ts b/packages/plugin-birdeye/src/providers/token/token-mint-burn-provider.ts new file mode 100644 index 0000000000..e60e8d7f9b --- /dev/null +++ b/packages/plugin-birdeye/src/providers/token/token-mint-burn-provider.ts @@ -0,0 +1,203 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + extractChain, + extractContractAddresses, + formatValue, + makeApiRequest, + shortenAddress, +} from "../utils"; + +// Types +interface MintBurnEvent { + type: "mint" | "burn"; + amount: number; + amountUSD: number; + timestamp: number; + txHash: string; + address: string; +} + +interface MintBurnResponse { + events: MintBurnEvent[]; + token: string; +} + +// Constants +const MINT_BURN_KEYWORDS = [ + "mint", + "burn", + "minting", + "burning", + "token supply", + "supply changes", + "token burns", + "token mints", + "supply history", + "mint history", + "burn history", + "supply events", +] as const; + +// Helper functions +const containsMintBurnKeyword = (text: string): boolean => { + return MINT_BURN_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getTokenMintBurnHistory = async ( + apiKey: string, + contractAddress: string, + chain: Chain +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + }); + const url = `${BASE_URL}/token/mint_burn?${params.toString()}`; + + elizaLogger.info( + `Fetching mint/burn history for ${contractAddress} on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { apiKey, chain }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error( + "Error fetching mint/burn history:", + error.message + ); + } + return null; + } +}; + +const formatEventData = (event: MintBurnEvent): string => { + const date = new Date(event.timestamp * 1000).toLocaleString(); + const eventType = event.type === "mint" ? "šŸŸ¢ Mint" : "šŸ”“ Burn"; + + let response = `${eventType} Event - ${date}\n`; + response += ` ā€¢ Amount: ${formatValue(event.amount)}\n`; + response += ` ā€¢ Value: ${formatValue(event.amountUSD)}\n`; + response += ` ā€¢ By: ${shortenAddress(event.address)}\n`; + response += ` ā€¢ Tx: ${shortenAddress(event.txHash)}`; + return response; +}; + +const analyzeMintBurnTrends = (events: MintBurnEvent[]): string => { + const mints = events.filter((e) => e.type === "mint"); + const burns = events.filter((e) => e.type === "burn"); + + const totalMinted = mints.reduce((sum, e) => sum + e.amount, 0); + const totalBurned = burns.reduce((sum, e) => sum + e.amount, 0); + const netChange = totalMinted - totalBurned; + + let analysis = "šŸ“Š Supply Change Analysis\n\n"; + + // Supply change metrics + analysis += `Total Minted: ${formatValue(totalMinted)}\n`; + analysis += `Total Burned: ${formatValue(totalBurned)}\n`; + analysis += `Net Change: ${formatValue(Math.abs(netChange))} ${netChange >= 0 ? "increase" : "decrease"}\n\n`; + + // Activity analysis + analysis += "Recent Activity:\n"; + if (mints.length === 0 && burns.length === 0) { + analysis += "ā€¢ No mint/burn activity in the period\n"; + } else { + if (mints.length > 0) { + analysis += `ā€¢ ${mints.length} mint events\n`; + } + if (burns.length > 0) { + analysis += `ā€¢ ${burns.length} burn events\n`; + } + } + + // Supply trend + if (netChange > 0) { + analysis += "\nšŸ“ˆ Supply is expanding"; + } else if (netChange < 0) { + analysis += "\nšŸ“‰ Supply is contracting"; + } else { + analysis += "\nāž”ļø Supply is stable"; + } + + return analysis; +}; + +const formatMintBurnResponse = ( + data: MintBurnResponse, + chain: Chain +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + + let response = `Mint/Burn History for ${data.token} on ${chainName}\n\n`; + + if (data.events.length === 0) { + return response + "No mint/burn events found."; + } + + response += analyzeMintBurnTrends(data.events); + response += "\n\nšŸ“œ Recent Events\n"; + + // Sort events by timestamp in descending order + const sortedEvents = [...data.events].sort( + (a, b) => b.timestamp - a.timestamp + ); + sortedEvents.forEach((event) => { + response += "\n" + formatEventData(event) + "\n"; + }); + + return response; +}; + +export const tokenMintBurnProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsMintBurnKeyword(messageText)) { + return null; + } + + const addresses = extractContractAddresses(messageText); + if (addresses.length === 0) { + return null; + } + + const chain = extractChain(messageText); + + elizaLogger.info( + `TOKEN MINT/BURN provider activated for ${addresses[0]} on ${chain}` + ); + + const mintBurnData = await getTokenMintBurnHistory( + apiKey, + addresses[0], + chain + ); + + if (!mintBurnData) { + return null; + } + + return formatMintBurnResponse(mintBurnData, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/token/token-overview-provider.ts b/packages/plugin-birdeye/src/providers/token/token-overview-provider.ts new file mode 100644 index 0000000000..594a72fe1e --- /dev/null +++ b/packages/plugin-birdeye/src/providers/token/token-overview-provider.ts @@ -0,0 +1,266 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; + +// Types +interface TokenExtensions { + website?: string; + twitter?: string; + telegram?: string; + discord?: string; + description?: string; + coingeckoId?: string; +} + +interface TokenOverview { + // Basic token info + address: string; + symbol: string; + name: string; + decimals: number; + logoURI: string; + + // Price and market data + price: number; + priceChange24hPercent: number; + liquidity: number; + marketCap: number; + realMc: number; + + // Supply info + supply: number; + circulatingSupply: number; + holder: number; + + // Volume data + v24h: number; + v24hUSD: number; + + // Social/metadata + extensions?: TokenExtensions; + + // Trading info + lastTradeUnixTime: number; + numberMarkets: number; +} + +// Constants +const OVERVIEW_KEYWORDS = [ + "overview", + "details", + "info", + "information", + "about", + "tell me about", + "what is", + "show me", +] as const; + +const CHAIN_KEYWORDS = [ + "solana", + "ethereum", + "arbitrum", + "avalanche", + "bsc", + "optimism", + "polygon", + "base", + "zksync", + "sui", +] as const; + +const BASE_URL = "https://public-api.birdeye.so"; + +// Helper functions +const containsOverviewKeyword = (text: string): boolean => { + return OVERVIEW_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const extractChain = (text: string): string => { + const chain = CHAIN_KEYWORDS.find((chain) => + text.toLowerCase().includes(chain.toLowerCase()) + ); + return chain || "solana"; +}; + +const extractContractAddress = (text: string): string | null => { + const words = text.split(/\s+/); + + for (const word of words) { + // Ethereum-like addresses (0x...) + if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { + return word; + } + // Solana addresses (base58, typically 32-44 chars) + if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { + return word; + } + } + return null; +}; + +const formatNumber = (num: number): string => { + if (!num && num !== 0) return "N/A"; + return num.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 6, + }); +}; + +const formatSocialLinks = (extensions?: TokenExtensions): string => { + if (!extensions) return ""; + + return Object.entries(extensions) + .filter(([key, value]) => { + try { + return Boolean( + value && + typeof value === "string" && + ["website", "twitter", "telegram", "discord"].includes( + key + ) + ); + } catch (err) { + elizaLogger.warn( + `Error processing social link for key ${key}:`, + err + ); + return false; + } + }) + .map(([key, value]) => { + try { + return `ā€¢ ${key.charAt(0).toUpperCase() + key.slice(1)}: ${value}`; + } catch (err) { + elizaLogger.error( + `Error formatting social link for ${key}:`, + err + ); + return ""; + } + }) + .filter(Boolean) + .join("\n"); +}; + +const formatTokenOverview = (token: TokenOverview, chain: string): string => { + const lastTradeTime = new Date( + token.lastTradeUnixTime * 1000 + ).toLocaleString(); + const socialLinks = formatSocialLinks(token.extensions); + + return `Token Overview for ${token.name} (${token.symbol}) on ${chain.charAt(0).toUpperCase() + chain.slice(1)} + +šŸ“Š Market Data +ā€¢ Current Price: $${formatNumber(token.price)} +ā€¢ 24h Change: ${formatNumber(token.priceChange24hPercent)}% +ā€¢ Market Cap: $${formatNumber(token.marketCap)} +ā€¢ Real Market Cap: $${formatNumber(token.realMc)} +ā€¢ Liquidity: $${formatNumber(token.liquidity)} + +šŸ“ˆ Trading Info +ā€¢ 24h Volume: $${formatNumber(token.v24hUSD)} +ā€¢ Number of Markets: ${token.numberMarkets} +ā€¢ Last Trade: ${lastTradeTime} + +šŸ’° Supply Information +ā€¢ Total Supply: ${formatNumber(token.supply)} +ā€¢ Circulating Supply: ${formatNumber(token.circulatingSupply)} +ā€¢ Number of Holders: ${token.holder ? formatNumber(token.holder) : "N/A"} + +šŸ”— Token Details +ā€¢ Contract: ${token.address} +ā€¢ Decimals: ${token.decimals} +${token.extensions?.description ? `ā€¢ Description: ${token.extensions.description}\n` : ""} +${socialLinks ? `\nšŸŒ Social Links\n${socialLinks}` : ""}`; +}; + +const getTokenOverview = async ( + apiKey: string, + contractAddress: string, + chain: string = "solana" +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + }); + const url = `${BASE_URL}/defi/token_overview?${params.toString()}`; + + elizaLogger.info( + `Fetching token overview for address ${contractAddress} on ${chain} from:`, + url + ); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + }, + }); + + if (!response.ok) { + if (response.status === 404) { + elizaLogger.warn( + `Token not found: ${contractAddress} on ${chain}` + ); + return null; + } + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.data; + } catch (error) { + elizaLogger.error("Error fetching token overview:", error); + return null; + } +}; + +export const tokenOverviewProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsOverviewKeyword(messageText)) { + return null; + } + + const contractAddress = extractContractAddress(messageText); + if (!contractAddress) { + return null; + } + + const chain = extractChain(messageText); + + elizaLogger.info( + `TOKEN OVERVIEW provider activated for address ${contractAddress} on ${chain}` + ); + + const tokenOverview = await getTokenOverview( + apiKey, + contractAddress, + chain + ); + + if (!tokenOverview) { + return null; + } + + return formatTokenOverview(tokenOverview, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/token/token-security-provider.ts b/packages/plugin-birdeye/src/providers/token/token-security-provider.ts new file mode 100644 index 0000000000..1affa34ad4 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/token/token-security-provider.ts @@ -0,0 +1,238 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + extractChain, + extractContractAddresses, + makeApiRequest, +} from "../utils"; + +// Types +interface SecurityData { + isHoneypot: boolean; + isProxy: boolean; + isVerified: boolean; + isAudited: boolean; + isRenounced: boolean; + isMintable: boolean; + isPausable: boolean; + hasBlacklist: boolean; + hasFeeOnTransfer: boolean; + transferFeePercentage: number; + riskLevel: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL"; + riskFactors: string[]; +} + +interface SecurityResponse { + data: SecurityData; + token: string; +} + +// Constants +const SECURITY_KEYWORDS = [ + "security", + "risk", + "audit", + "safety", + "honeypot", + "scam", + "safe", + "verified", + "contract security", + "token security", + "token safety", + "token risk", + "token audit", + "security check", + "risk check", + "safety check", +] as const; + +// Helper functions +const containsSecurityKeyword = (text: string): boolean => { + return SECURITY_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getTokenSecurity = async ( + apiKey: string, + contractAddress: string, + chain: Chain +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + }); + const url = `${BASE_URL}/token/security?${params.toString()}`; + + elizaLogger.info( + `Fetching security data for ${contractAddress} on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { apiKey, chain }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching security data:", error.message); + } + return null; + } +}; + +const getRiskEmoji = (riskLevel: SecurityData["riskLevel"]): string => { + switch (riskLevel) { + case "LOW": + return "āœ…"; + case "MEDIUM": + return "āš ļø"; + case "HIGH": + return "šŸšØ"; + case "CRITICAL": + return "šŸ’€"; + default: + return "ā“"; + } +}; + +const formatSecurityFeatures = (data: SecurityData): string => { + const features = [ + { name: "Contract Verified", value: data.isVerified }, + { name: "Contract Audited", value: data.isAudited }, + { name: "Ownership Renounced", value: data.isRenounced }, + { name: "Mintable Token", value: data.isMintable }, + { name: "Pausable Token", value: data.isPausable }, + { name: "Has Blacklist", value: data.hasBlacklist }, + { name: "Has Transfer Fee", value: data.hasFeeOnTransfer }, + ]; + + return features + .map(({ name, value }) => `ā€¢ ${name}: ${value ? "āœ…" : "āŒ"}`) + .join("\n"); +}; + +const analyzeSecurityRisks = (data: SecurityData): string => { + let analysis = ""; + + // Critical checks + if (data.isHoneypot) { + analysis += + "šŸš« CRITICAL: Token is identified as a honeypot! DO NOT TRADE.\n"; + } + + if (data.isProxy) { + analysis += + "āš ļø Contract is upgradeable (proxy). Owner can modify functionality.\n"; + } + + // Fee analysis + if (data.hasFeeOnTransfer) { + const feeLevel = data.transferFeePercentage > 5 ? "High" : "Standard"; + analysis += `šŸ’ø ${feeLevel} transfer fee (${data.transferFeePercentage}%).\n`; + } + + // Contract security + if (!data.isVerified) { + analysis += "āš ļø Contract is not verified. Cannot audit code.\n"; + } + if (!data.isAudited) { + analysis += "āš ļø No professional audit found.\n"; + } + if (!data.isRenounced) { + analysis += + "šŸ‘¤ Contract ownership retained. Owner can modify contract.\n"; + } + + // Token features + if (data.isMintable) { + analysis += "šŸ“ˆ Token supply can be increased by owner.\n"; + } + if (data.isPausable) { + analysis += "āøļø Trading can be paused by owner.\n"; + } + if (data.hasBlacklist) { + analysis += "šŸš« Addresses can be blacklisted from trading.\n"; + } + + // Risk factors + if (data.riskFactors.length > 0) { + analysis += "\nIdentified Risk Factors:\n"; + data.riskFactors.forEach((factor) => { + analysis += `ā€¢ ${factor}\n`; + }); + } + + return analysis; +}; + +const formatSecurityResponse = ( + data: SecurityResponse, + chain: Chain +): string => { + const { data: securityData } = data; + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + + let response = `Security Analysis for ${data.token} on ${chainName}\n\n`; + + // Overall Risk Level + response += `šŸŽÆ Risk Level: ${getRiskEmoji(securityData.riskLevel)} ${securityData.riskLevel}\n\n`; + + // Security Analysis + response += "šŸ” Security Analysis\n"; + response += analyzeSecurityRisks(securityData) + "\n\n"; + + // Contract Features + response += "šŸ“‹ Contract Features\n"; + response += formatSecurityFeatures(securityData); + + return response; +}; + +export const tokenSecurityProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsSecurityKeyword(messageText)) { + return null; + } + + const addresses = extractContractAddresses(messageText); + if (addresses.length === 0) { + return null; + } + + const chain = extractChain(messageText); + + elizaLogger.info( + `TOKEN SECURITY provider activated for ${addresses[0]} on ${chain}` + ); + + const securityData = await getTokenSecurity( + apiKey, + addresses[0], + chain + ); + + if (!securityData) { + return null; + } + + return formatSecurityResponse(securityData, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/token/token-trade-provider.ts b/packages/plugin-birdeye/src/providers/token/token-trade-provider.ts new file mode 100644 index 0000000000..8c19da3f12 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/token/token-trade-provider.ts @@ -0,0 +1,327 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; + +// Types +interface Trade { + timestamp: number; + price: number; + volume: number; + side: "buy" | "sell"; + source: string; + txHash: string; + buyer?: string; + seller?: string; +} + +interface TokenTradeData { + trades: Trade[]; + totalCount: number; + token: string; +} + +interface MultiTokenTradeData { + [tokenAddress: string]: TokenTradeData; +} + +// Constants +const TRADE_KEYWORDS = [ + "trades", + "trading", + "transactions", + "swaps", + "buys", + "sells", + "orders", + "executions", + "trade history", + "trading history", + "recent trades", +] as const; + +const CHAIN_KEYWORDS = [ + "solana", + "ethereum", + "arbitrum", + "avalanche", + "bsc", + "optimism", + "polygon", + "base", + "zksync", + "sui", +] as const; + +const BASE_URL = "https://public-api.birdeye.so"; + +// Helper functions +const containsTradeKeyword = (text: string): boolean => { + return TRADE_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const extractChain = (text: string): string => { + const chain = CHAIN_KEYWORDS.find((chain) => + text.toLowerCase().includes(chain.toLowerCase()) + ); + return chain || "solana"; +}; + +const extractContractAddresses = (text: string): string[] => { + const words = text.split(/\s+/); + const addresses: string[] = []; + + for (const word of words) { + // Ethereum-like addresses (0x...) + if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { + addresses.push(word); + } + // Solana addresses (base58, typically 32-44 chars) + if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { + addresses.push(word); + } + } + return addresses; +}; + +const getSingleTokenTrades = async ( + apiKey: string, + contractAddress: string, + chain: string = "solana", + limit: number = 10 +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + limit: limit.toString(), + }); + const url = `${BASE_URL}/token/trade_data_single?${params.toString()}`; + + elizaLogger.info( + `Fetching trade data for token ${contractAddress} on ${chain} from:`, + url + ); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + }, + }); + + if (!response.ok) { + if (response.status === 404) { + elizaLogger.warn( + `Token not found: ${contractAddress} on ${chain}` + ); + return null; + } + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.data; + } catch (error) { + elizaLogger.error("Error fetching token trade data:", error); + return null; + } +}; + +const getMultipleTokenTrades = async ( + apiKey: string, + addresses: string[], + chain: string = "solana", + limit: number = 5 +): Promise => { + try { + const params = new URLSearchParams({ + addresses: addresses.join(","), + limit: limit.toString(), + }); + const url = `${BASE_URL}/token/trade_data_multiple?${params.toString()}`; + + elizaLogger.info( + `Fetching trade data for ${addresses.length} tokens on ${chain} from:`, + url + ); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.data; + } catch (error) { + elizaLogger.error("Error fetching multiple token trade data:", error); + return null; + } +}; + +const formatValue = (value: number): string => { + if (value >= 1_000_000_000) { + return `$${(value / 1_000_000_000).toFixed(2)}B`; + } + if (value >= 1_000_000) { + return `$${(value / 1_000_000).toFixed(2)}M`; + } + if (value >= 1_000) { + return `$${(value / 1_000).toFixed(2)}K`; + } + return `$${value.toFixed(2)}`; +}; + +const shortenAddress = (address: string): string => { + if (!address || address.length <= 12) return address || "Unknown"; + return `${address.slice(0, 6)}...${address.slice(-4)}`; +}; + +const formatTrade = (trade: Trade): string => { + const timestamp = new Date(trade.timestamp * 1000).toLocaleString(); + const priceFormatted = + trade.price != null + ? trade.price < 0.01 + ? trade.price.toExponential(2) + : trade.price.toFixed(2) + : "N/A"; + const side = trade.side === "buy" ? "šŸŸ¢ Buy" : "šŸ”“ Sell"; + + let response = `${side} - ${timestamp}\n`; + response += `ā€¢ Price: $${priceFormatted}\n`; + response += `ā€¢ Volume: ${trade.volume ? formatValue(trade.volume) : "N/A"}\n`; + response += `ā€¢ Source: ${trade.source || "Unknown"}\n`; + if (trade.buyer && trade.seller) { + response += `ā€¢ Buyer: ${shortenAddress(trade.buyer)}\n`; + response += `ā€¢ Seller: ${shortenAddress(trade.seller)}\n`; + } + response += `ā€¢ Tx: ${trade.txHash ? shortenAddress(trade.txHash) : "N/A"}`; + + return response; +}; + +const formatSingleTokenTradeResponse = ( + data: TokenTradeData, + chain: string +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + let response = `Recent Trades for ${data.token} on ${chainName}:\n\n`; + + if (data.trades.length === 0) { + return response + "No recent trades found."; + } + + data.trades.forEach((trade, index) => { + response += `${index + 1}. ${formatTrade(trade)}\n\n`; + }); + + if (data.totalCount > data.trades.length) { + response += `Showing ${data.trades.length} of ${data.totalCount} total trades.`; + } + + return response; +}; + +const formatMultipleTokenTradeResponse = ( + data: MultiTokenTradeData, + chain: string +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + let response = `Recent Trades on ${chainName}:\n\n`; + + const tokens = Object.entries(data); + if (tokens.length === 0) { + return response + "No trades found for any token."; + } + + tokens.forEach(([address, tokenData]) => { + response += `${tokenData.token} (${shortenAddress(address)}):\n`; + + if (tokenData.trades.length === 0) { + response += "No recent trades\n\n"; + return; + } + + tokenData.trades.forEach((trade, index) => { + response += `${index + 1}. ${formatTrade(trade)}\n`; + }); + + if (tokenData.totalCount > tokenData.trades.length) { + response += `Showing ${tokenData.trades.length} of ${tokenData.totalCount} trades\n`; + } + response += "\n"; + }); + + return response; +}; + +export const tokenTradeProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsTradeKeyword(messageText)) { + return null; + } + + const addresses = extractContractAddresses(messageText); + if (addresses.length === 0) { + return null; + } + + const chain = extractChain(messageText); + + if (addresses.length === 1) { + elizaLogger.info( + `TOKEN TRADE provider activated for address ${addresses[0]} on ${chain}` + ); + + const tradeData = await getSingleTokenTrades( + apiKey, + addresses[0], + chain + ); + + if (!tradeData) { + return null; + } + + return formatSingleTokenTradeResponse(tradeData, chain); + } else { + elizaLogger.info( + `MULTIPLE TOKEN TRADE provider activated for ${addresses.length} addresses on ${chain}` + ); + + const tradeData = await getMultipleTokenTrades( + apiKey, + addresses, + chain + ); + + if (!tradeData) { + return null; + } + + return formatMultipleTokenTradeResponse(tradeData, chain); + } + }, +}; diff --git a/packages/plugin-birdeye/src/providers/token/top-traders-provider.ts b/packages/plugin-birdeye/src/providers/token/top-traders-provider.ts new file mode 100644 index 0000000000..ec8c9824e8 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/token/top-traders-provider.ts @@ -0,0 +1,104 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { BASE_URL, Chain, makeApiRequest } from "../utils"; + +// Types +interface Trader { + address: string; + tradeCount: number; + volume: number; + profit: number; + lastTradeTime: number; +} + +interface TopTradersResponse { + traders: Trader[]; +} + +// Constants +const TOP_TRADERS_KEYWORDS = [ + "top traders", + "best traders", + "leading traders", + "most successful traders", + "highest volume traders", +] as const; + +// Helper functions +const containsTopTradersKeyword = (text: string): boolean => { + return TOP_TRADERS_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getTopTraders = async ( + apiKey: string, + chain: Chain = "solana" +): Promise => { + try { + const url = `${BASE_URL}/token/top_traders`; + + elizaLogger.info("Fetching top traders from:", url); + + return await makeApiRequest(url, { apiKey, chain }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching top traders:", error.message); + } + return null; + } +}; + +const formatTopTradersResponse = (data: TopTradersResponse): string => { + let response = "šŸ† Top Traders\n\n"; + + data.traders.forEach((trader, index) => { + const lastTradeDate = new Date( + trader.lastTradeTime * 1000 + ).toLocaleString(); + const profitPrefix = trader.profit >= 0 ? "+" : ""; + + response += `${index + 1}. Trader ${trader.address.slice(0, 8)}...${trader.address.slice(-6)}\n`; + response += `ā€¢ Trade Count: ${trader.tradeCount.toLocaleString()}\n`; + response += `ā€¢ Volume: $${trader.volume.toLocaleString()}\n`; + response += `ā€¢ Profit: ${profitPrefix}$${trader.profit.toLocaleString()}\n`; + response += `ā€¢ Last Trade: ${lastTradeDate}\n\n`; + }); + + return response.trim(); +}; + +export const topTradersProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsTopTradersKeyword(messageText)) { + return null; + } + + elizaLogger.info("TOP_TRADERS provider activated"); + + const tradersData = await getTopTraders(apiKey); + + if (!tradersData) { + return null; + } + + return formatTopTradersResponse(tradersData); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/token/trending-tokens-provider.ts b/packages/plugin-birdeye/src/providers/token/trending-tokens-provider.ts new file mode 100644 index 0000000000..afe3134653 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/token/trending-tokens-provider.ts @@ -0,0 +1,270 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; + +interface TrendingToken { + address: string; + name: string; + symbol: string; + decimals: number; + volume24hUSD: number; + liquidity: number; + logoURI: string; + price: number; +} + +const TRENDING_KEYWORDS = [ + "trending", + "popular", + "hot", + "top", + "performing", + "movers", + "gainers", + "volume", + "liquidity", + "market cap", + "price action", +]; + +const TOKEN_KEYWORDS = [ + "token", + "tokens", + "coin", + "coins", + "crypto", + "cryptocurrency", + "asset", + "assets", + "sol", + "solana", +]; + +const ASCENDING_KEYWORDS = [ + "lowest", + "worst", + "bottom", + "least", + "smallest", + "weakest", +]; + +const PAGINATION_KEYWORDS = [ + "more", + "additional", + "next", + "other", + "show more", + "continue", +]; + +const SUPPORTED_CHAINS = [ + "solana", + "ethereum", + "arbitrum", + "avalanche", + "bsc", + "optimism", + "polygon", + "base", + "zksync", + "sui", +]; + +const BASE_URL = "https://public-api.birdeye.so"; + +interface GetTrendingTokensOptions { + sort_by?: "volume24hUSD" | "rank" | "liquidity"; + sort_type?: "desc" | "asc"; + offset?: number; + limit?: number; + min_liquidity?: number; + chain?: string; +} + +const getTrendingTokens = async ( + apiKey: string, + options: GetTrendingTokensOptions = {} +): Promise => { + try { + const { + sort_by = "volume24hUSD", + sort_type = "desc", + offset = 0, + limit = 10, + min_liquidity = 1000, + chain = "solana", + } = options; + + const params = new URLSearchParams({ + sort_by, + sort_type, + offset: offset.toString(), + limit: limit.toString(), + min_liquidity: min_liquidity.toString(), + }); + + const url = `${BASE_URL}/defi/token_trending?${params.toString()}`; + elizaLogger.info("Fetching trending tokens from:", url); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return (await response.json()).data.tokens; + } catch (error) { + elizaLogger.error("Error fetching trending tokens:", error); + throw error; + } +}; + +const formatTrendingTokensToString = ( + tokens: TrendingToken[], + chain: string +): string => { + if (!tokens.length) { + return "No trending tokens found."; + } + + const formattedTokens = tokens + .map((token, index) => { + const priceFormatted = + token.price != null + ? token.price < 0.01 + ? token.price.toExponential(2) + : token.price.toFixed(2) + : "N/A"; + + const volume = + token.volume24hUSD != null + ? `$${(token.volume24hUSD / 1_000_000).toFixed(2)}M` + : "N/A"; + + const liquidity = + token.liquidity != null + ? `$${(token.liquidity / 1_000_000).toFixed(2)}M` + : "N/A"; + + return ( + `${index + 1}. ${token.name || "Unknown"} (${token.symbol || "N/A"}):\n` + + ` Price: ${priceFormatted}\n` + + ` Volume 24h: ${volume}\n` + + ` Liquidity: ${liquidity}` + ); + }) + .join("\n\n"); + + return `Here are the trending tokens on ${chain.charAt(0).toUpperCase() + chain.slice(1)}:\n\n${formattedTokens}`; +}; + +export const trendingTokensProvider: Provider = { + get: async (runtime: IAgentRuntime, message: Memory, state?: State) => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + return null; + } + + const messageText = message.content.text.toLowerCase(); + + // Check if message contains trending-related keywords + const hasTrendingKeyword = TRENDING_KEYWORDS.some((keyword) => + messageText.includes(keyword) + ); + + // Check if message contains token-related keywords + const hasTokenKeyword = TOKEN_KEYWORDS.some((keyword) => + messageText.includes(keyword) + ); + + // Check if the message is a direct question about trends + const isQuestionAboutTrends = + messageText.includes("?") && + (messageText.includes("what") || + messageText.includes("which") || + messageText.includes("show")) && + hasTrendingKeyword; + + // Check recent conversation context from state + const recentMessages = (state?.recentMessagesData || []) as Memory[]; + const isInTrendingConversation = recentMessages.some( + (msg) => + msg.content?.text?.toLowerCase().includes("trending") || + msg.content?.text?.toLowerCase().includes("token") + ); + + // Determine sorting direction based on keywords + const isAscending = ASCENDING_KEYWORDS.some((keyword) => + messageText.includes(keyword) + ); + const sortType = isAscending ? "asc" : "desc"; + + // Determine if this is a pagination request + const isPaginationRequest = PAGINATION_KEYWORDS.some((keyword) => + messageText.includes(keyword) + ); + + // Get the current offset from state or default to 0 + const currentOffset = (state?.trendingTokensOffset as number) || 0; + const offset = isPaginationRequest ? currentOffset + 10 : 0; + + // Determine sort criteria based on message content + let sortBy: "volume24hUSD" | "rank" | "liquidity" = "volume24hUSD"; + if (messageText.includes("liquidity")) { + sortBy = "liquidity"; + } else if (messageText.includes("rank")) { + sortBy = "rank"; + } + + // Determine which chain is being asked about + const requestedChain = + SUPPORTED_CHAINS.find((chain) => + messageText.includes(chain.toLowerCase()) + ) || "solana"; + + // Combine signals to make decision + const shouldProvideData = + // Direct questions about trends + isQuestionAboutTrends || + // Explicit mentions of trending tokens + (hasTrendingKeyword && hasTokenKeyword) || + // Follow-up in a trending conversation + (isInTrendingConversation && hasTokenKeyword) || + // Pagination request in conversation context + (isPaginationRequest && isInTrendingConversation); + + if (!shouldProvideData) { + return null; + } + + elizaLogger.info( + `TRENDING TOKENS provider activated for ${requestedChain} trending tokens query` + ); + + const trendingTokens = await getTrendingTokens(apiKey, { + sort_by: sortBy, + sort_type: sortType, + offset, + limit: 10, + min_liquidity: 1000, + chain: requestedChain, + }); + + const formattedTrending = formatTrendingTokensToString( + trendingTokens, + requestedChain + ); + + return formattedTrending; + }, +}; diff --git a/packages/plugin-birdeye/src/providers/trader/gainers-losers-provider.ts b/packages/plugin-birdeye/src/providers/trader/gainers-losers-provider.ts new file mode 100644 index 0000000000..c9998243b1 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/trader/gainers-losers-provider.ts @@ -0,0 +1,228 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; + +// Types +interface TokenMarketData { + address: string; + symbol: string; + name: string; + price: number; + priceChange24h: number; + priceChange24hPercent: number; + volume24h: number; + marketCap: number; + liquidity: number; + logoURI?: string; +} + +interface GainersLosersData { + gainers: TokenMarketData[]; + losers: TokenMarketData[]; + timestamp: number; +} + +// Constants +const GAINERS_KEYWORDS = [ + "gainers", + "top gainers", + "best performing", + "biggest gains", + "movers up", + "green", + "pumping", + "rising", +] as const; + +const LOSERS_KEYWORDS = [ + "losers", + "top losers", + "worst performing", + "biggest losses", + "movers down", + "red", + "dumping", + "falling", +] as const; + +const CHAIN_KEYWORDS = [ + "solana", + "ethereum", + "arbitrum", + "avalanche", + "bsc", + "optimism", + "polygon", + "base", + "zksync", + "sui", +] as const; + +const BASE_URL = "https://public-api.birdeye.so"; + +// Helper functions +const containsGainersKeyword = (text: string): boolean => { + return GAINERS_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const containsLosersKeyword = (text: string): boolean => { + return LOSERS_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const extractChain = (text: string): string => { + const chain = CHAIN_KEYWORDS.find((chain) => + text.toLowerCase().includes(chain.toLowerCase()) + ); + return chain || "solana"; +}; + +const getGainersLosers = async ( + apiKey: string, + chain: string = "solana" +): Promise => { + try { + const params = new URLSearchParams({ + limit: "10", // Get top 10 gainers and losers + }); + const url = `${BASE_URL}/trader/gainers-losers?${params.toString()}`; + + elizaLogger.info(`Fetching gainers/losers on ${chain} from:`, url); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.data; + } catch (error) { + elizaLogger.error("Error fetching gainers/losers:", error); + return null; + } +}; + +const formatValue = (value: number): string => { + if (value >= 1_000_000_000) { + return `$${(value / 1_000_000_000).toFixed(2)}B`; + } + if (value >= 1_000_000) { + return `$${(value / 1_000_000).toFixed(2)}M`; + } + if (value >= 1_000) { + return `$${(value / 1_000).toFixed(2)}K`; + } + return `$${value.toFixed(2)}`; +}; + +const formatTokenData = (token: TokenMarketData): string => { + const priceFormatted = + token.price < 0.01 + ? token.price.toExponential(2) + : token.price.toFixed(2); + + return ( + `ā€¢ ${token.symbol} (${token.name})\n` + + ` Price: $${priceFormatted}\n` + + ` 24h Change: ${token.priceChange24hPercent.toFixed(2)}% (${formatValue(token.priceChange24h)})\n` + + ` Volume: ${formatValue(token.volume24h)}\n` + + ` Market Cap: ${formatValue(token.marketCap)}\n` + + ` Liquidity: ${formatValue(token.liquidity)}` + ); +}; + +const formatGainersLosersResponse = ( + data: GainersLosersData, + chain: string, + showGainers: boolean, + showLosers: boolean +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + let response = `Market Movers on ${chainName}\n`; + response += `Last Updated: ${new Date(data.timestamp * 1000).toLocaleString()}\n\n`; + + if (showGainers && Array.isArray(data.gainers) && data.gainers.length > 0) { + response += `šŸ“ˆ Top Gainers:\n`; + data.gainers.forEach((token, index) => { + response += `\n${index + 1}. ${formatTokenData(token)}\n`; + }); + } + + if (showLosers && Array.isArray(data.losers) && data.losers.length > 0) { + if ( + showGainers && + Array.isArray(data.gainers) && + data.gainers.length > 0 + ) + response += "\n"; + response += `šŸ“‰ Top Losers:\n`; + data.losers.forEach((token, index) => { + response += `\n${index + 1}. ${formatTokenData(token)}\n`; + }); + } + + if ( + (!data.gainers?.length && !data.losers?.length) || + (showGainers && !data.gainers?.length && !showLosers) || + (showLosers && !data.losers?.length && !showGainers) + ) { + response += "No market data available at this time."; + } + + return response; +}; + +export const gainersLosersProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + const showGainers = containsGainersKeyword(messageText); + const showLosers = containsLosersKeyword(messageText); + + // If neither gainers nor losers are specifically mentioned, show both + const showBoth = !showGainers && !showLosers; + + if (!showGainers && !showLosers && !showBoth) { + return null; + } + + const chain = extractChain(messageText); + + elizaLogger.info(`GAINERS/LOSERS provider activated for ${chain}`); + + const marketData = await getGainersLosers(apiKey, chain); + + if (!marketData) { + return null; + } + + return formatGainersLosersResponse( + marketData, + chain, + showGainers || showBoth, + showLosers || showBoth + ); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/trader/index.ts b/packages/plugin-birdeye/src/providers/trader/index.ts new file mode 100644 index 0000000000..b5889ef376 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/trader/index.ts @@ -0,0 +1,2 @@ +export * from "./gainers-losers-provider"; +export * from "./trades-seek-provider"; diff --git a/packages/plugin-birdeye/src/providers/trader/trades-seek-provider.ts b/packages/plugin-birdeye/src/providers/trader/trades-seek-provider.ts new file mode 100644 index 0000000000..20db87c394 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/trader/trades-seek-provider.ts @@ -0,0 +1,247 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; + +// Types +interface Trade { + timestamp: number; + token: string; + tokenAddress: string; + price: number; + volume: number; + side: "buy" | "sell"; + source: string; + txHash: string; + buyer?: string; + seller?: string; +} + +interface TradesResponse { + trades: Trade[]; + totalCount: number; +} + +// Constants +const TIME_SEEK_KEYWORDS = [ + "trades since", + "trades after", + "trades before", + "trades from", + "trades at", + "trading since", + "trading after", + "trading before", + "trading from", + "trading at", + "transactions since", + "transactions after", + "transactions before", + "transactions from", + "transactions at", +] as const; + +const TIME_UNITS = { + second: 1, + minute: 60, + hour: 3600, + day: 86400, + week: 604800, + month: 2592000, +} as const; + +const CHAIN_KEYWORDS = [ + "solana", + "ethereum", + "arbitrum", + "avalanche", + "bsc", + "optimism", + "polygon", + "base", + "zksync", + "sui", +] as const; + +const BASE_URL = "https://public-api.birdeye.so"; + +// Helper functions +const containsTimeSeekKeyword = (text: string): boolean => { + return TIME_SEEK_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const extractChain = (text: string): string => { + const chain = CHAIN_KEYWORDS.find((chain) => + text.toLowerCase().includes(chain.toLowerCase()) + ); + return chain || "solana"; +}; + +const extractTimeFromText = (text: string): number | null => { + // Try to find time expressions like "1 hour ago", "2 days ago", etc. + const timeRegex = /(\d+)\s*(second|minute|hour|day|week|month)s?\s*ago/i; + const match = text.match(timeRegex); + + if (match) { + const amount = parseInt(match[1]); + const unit = match[2].toLowerCase() as keyof typeof TIME_UNITS; + const now = Math.floor(Date.now() / 1000); + return now - amount * TIME_UNITS[unit]; + } + + // Try to find specific date/time + const dateMatch = text.match(/(\d{4}-\d{2}-\d{2}|\d{2}\/\d{2}\/\d{4})/); + if (dateMatch) { + const date = new Date(dateMatch[1]); + if (!isNaN(date.getTime())) { + return Math.floor(date.getTime() / 1000); + } + } + + return null; +}; + +const getTradesByTime = async ( + apiKey: string, + timestamp: number, + chain: string = "solana", + limit: number = 10 +): Promise => { + try { + const params = new URLSearchParams({ + timestamp: timestamp.toString(), + limit: limit.toString(), + }); + const url = `${BASE_URL}/trader/trades_seek_time?${params.toString()}`; + + elizaLogger.info( + `Fetching trades since ${new Date(timestamp * 1000).toLocaleString()} on ${chain} from:`, + url + ); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.data; + } catch (error) { + elizaLogger.error("Error fetching trades by time:", error); + return null; + } +}; + +const formatValue = (value: number): string => { + if (value >= 1_000_000_000) { + return `$${(value / 1_000_000_000).toFixed(2)}B`; + } + if (value >= 1_000_000) { + return `$${(value / 1_000_000).toFixed(2)}M`; + } + if (value >= 1_000) { + return `$${(value / 1_000).toFixed(2)}K`; + } + return `$${value.toFixed(2)}`; +}; + +const shortenAddress = (address: string): string => { + if (!address || address.length <= 12) return address || "Unknown"; + return `${address.slice(0, 6)}...${address.slice(-4)}`; +}; + +const formatTrade = (trade: Trade): string => { + const timestamp = new Date(trade.timestamp * 1000).toLocaleString(); + const priceFormatted = + trade.price < 0.01 + ? trade.price.toExponential(2) + : trade.price.toFixed(2); + const side = trade.side === "buy" ? "šŸŸ¢ Buy" : "šŸ”“ Sell"; + + let response = `${side} ${trade.token} - ${timestamp}\n`; + response += `ā€¢ Token: ${shortenAddress(trade.tokenAddress)}\n`; + response += `ā€¢ Price: $${priceFormatted}\n`; + response += `ā€¢ Volume: ${formatValue(trade.volume)}\n`; + response += `ā€¢ Source: ${trade.source}\n`; + if (trade.buyer && trade.seller) { + response += `ā€¢ Buyer: ${shortenAddress(trade.buyer)}\n`; + response += `ā€¢ Seller: ${shortenAddress(trade.seller)}\n`; + } + response += `ā€¢ Tx: ${shortenAddress(trade.txHash)}`; + + return response; +}; + +const formatTradesResponse = ( + data: TradesResponse, + timestamp: number, + chain: string +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + const fromTime = new Date(timestamp * 1000).toLocaleString(); + let response = `Trades on ${chainName} since ${fromTime}:\n\n`; + + if (data.trades.length === 0) { + return response + "No trades found in this time period."; + } + + data.trades.forEach((trade, index) => { + response += `${index + 1}. ${formatTrade(trade)}\n\n`; + }); + + if (data.totalCount > data.trades.length) { + response += `Showing ${data.trades.length} of ${data.totalCount} total trades.`; + } + + return response; +}; + +export const tradesSeekProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsTimeSeekKeyword(messageText)) { + return null; + } + + const timestamp = extractTimeFromText(messageText); + if (!timestamp) { + return null; + } + + const chain = extractChain(messageText); + + elizaLogger.info( + `TRADES SEEK provider activated for time ${new Date(timestamp * 1000).toLocaleString()} on ${chain}` + ); + + const tradesData = await getTradesByTime(apiKey, timestamp, chain); + + if (!tradesData) { + return null; + } + + return formatTradesResponse(tradesData, timestamp, chain); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/utils.ts b/packages/plugin-birdeye/src/providers/utils.ts new file mode 100644 index 0000000000..3e7a5d1252 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/utils.ts @@ -0,0 +1,298 @@ +import { elizaLogger } from "@elizaos/core"; + +// Constants +export const BASE_URL = "https://public-api.birdeye.so"; + +export const CHAIN_KEYWORDS = [ + "solana", + "ethereum", + "arbitrum", + "avalanche", + "bsc", + "optimism", + "polygon", + "base", + "zksync", + "sui", +] as const; + +// Types +export type Chain = (typeof CHAIN_KEYWORDS)[number]; + +export class BirdeyeApiError extends Error { + constructor( + public status: number, + message: string + ) { + super(message); + this.name = "BirdeyeApiError"; + } +} + +export interface ApiResponse { + success: boolean; + data: T; + error?: string; +} + +// Time-related types and constants +export const TIME_UNITS = { + second: 1, + minute: 60, + hour: 3600, + day: 86400, + week: 604800, + month: 2592000, +} as const; + +export const TIMEFRAME_KEYWORDS = { + "1m": 60, + "3m": 180, + "5m": 300, + "15m": 900, + "30m": 1800, + "1h": 3600, + "2h": 7200, + "4h": 14400, + "6h": 21600, + "12h": 43200, + "1d": 86400, + "1w": 604800, +} as const; + +export type TimeUnit = keyof typeof TIME_UNITS; +export type Timeframe = keyof typeof TIMEFRAME_KEYWORDS; + +// Helper functions +export const extractChain = (text: string): Chain => { + const chain = CHAIN_KEYWORDS.find((chain) => + text.toLowerCase().includes(chain.toLowerCase()) + ); + return (chain || "solana") as Chain; +}; + +export const extractContractAddresses = (text: string): string[] => { + const words = text.split(/\s+/); + const addresses: string[] = []; + + for (const word of words) { + // Ethereum-like addresses (0x...) + if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { + addresses.push(word); + } + // Solana addresses (base58, typically 32-44 chars) + if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { + addresses.push(word); + } + } + return addresses; +}; + +// Time extraction and analysis +export const extractTimeframe = (text: string): Timeframe => { + // First, check for explicit timeframe mentions + const timeframe = Object.keys(TIMEFRAME_KEYWORDS).find((tf) => + text.toLowerCase().includes(tf.toLowerCase()) + ); + if (timeframe) return timeframe as Timeframe; + + // Check for semantic timeframe hints + const semanticMap = { + "short term": "15m", + "medium term": "1h", + "long term": "1d", + intraday: "1h", + daily: "1d", + weekly: "1w", + detailed: "5m", + quick: "15m", + overview: "1d", + } as const; + + for (const [hint, tf] of Object.entries(semanticMap)) { + if (text.toLowerCase().includes(hint)) { + return tf as Timeframe; + } + } + + // Analyze for time-related words + if (text.match(/minute|min|minutes/i)) return "15m"; + if (text.match(/hour|hourly|hours/i)) return "1h"; + if (text.match(/day|daily|24h/i)) return "1d"; + if (text.match(/week|weekly/i)) return "1w"; + + // Default based on context + if (text.match(/trade|trades|trading|recent/i)) return "15m"; + if (text.match(/trend|analysis|analyze/i)) return "1h"; + if (text.match(/history|historical|long|performance/i)) return "1d"; + + return "1h"; // Default timeframe +}; + +export const extractTimeRange = ( + text: string +): { start: number; end: number } => { + const now = Math.floor(Date.now() / 1000); + + // Check for specific date ranges + const dateRangeMatch = text.match( + /from\s+(\d{4}-\d{2}-\d{2})\s+to\s+(\d{4}-\d{2}-\d{2})/i + ); + if (dateRangeMatch) { + const start = new Date(dateRangeMatch[1]).getTime() / 1000; + const end = new Date(dateRangeMatch[2]).getTime() / 1000; + return { start, end }; + } + + // Check for relative time expressions + const timeRegex = /(\d+)\s*(second|minute|hour|day|week|month)s?\s*ago/i; + const match = text.match(timeRegex); + if (match) { + const amount = parseInt(match[1]); + const unit = match[2].toLowerCase() as TimeUnit; + const start = now - amount * TIME_UNITS[unit]; + return { start, end: now }; + } + + // Check for semantic time ranges + const semanticRanges: Record = { + today: TIME_UNITS.day, + "this week": TIME_UNITS.week, + "this month": TIME_UNITS.month, + recent: TIME_UNITS.hour * 4, + latest: TIME_UNITS.hour, + "last hour": TIME_UNITS.hour, + "last day": TIME_UNITS.day, + "last week": TIME_UNITS.week, + "last month": TIME_UNITS.month, + }; + + for (const [range, duration] of Object.entries(semanticRanges)) { + if (text.toLowerCase().includes(range)) { + return { start: now - duration, end: now }; + } + } + + // Analyze context for appropriate default range + if (text.match(/trend|analysis|performance/i)) { + return { start: now - TIME_UNITS.week, end: now }; // 1 week for analysis + } + if (text.match(/trade|trades|trading|recent/i)) { + return { start: now - TIME_UNITS.day, end: now }; // 1 day for trading + } + if (text.match(/history|historical|long term/i)) { + return { start: now - TIME_UNITS.month, end: now }; // 1 month for history + } + + // Default to last 24 hours + return { start: now - TIME_UNITS.day, end: now }; +}; + +export const extractLimit = (text: string): number => { + // Check for explicit limit mentions + const limitMatch = text.match( + /\b(show|display|get|fetch|limit)\s+(\d+)\b/i + ); + if (limitMatch) { + const limit = parseInt(limitMatch[2]); + return Math.min(Math.max(limit, 1), 100); // Clamp between 1 and 100 + } + + // Check for semantic limit hints + if (text.match(/\b(all|everything|full|complete)\b/i)) return 100; + if (text.match(/\b(brief|quick|summary|overview)\b/i)) return 5; + if (text.match(/\b(detailed|comprehensive)\b/i)) return 50; + + // Default based on context + if (text.match(/\b(trade|trades|trading)\b/i)) return 10; + if (text.match(/\b(analysis|analyze|trend)\b/i)) return 24; + if (text.match(/\b(history|historical)\b/i)) return 50; + + return 10; // Default limit +}; + +// Formatting helpers +export const formatValue = (value: number): string => { + if (value >= 1_000_000_000) { + return `$${(value / 1_000_000_000).toFixed(2)}B`; + } + if (value >= 1_000_000) { + return `$${(value / 1_000_000).toFixed(2)}M`; + } + if (value >= 1_000) { + return `$${(value / 1_000).toFixed(2)}K`; + } + return `$${value.toFixed(2)}`; +}; + +export const formatPercentChange = (change?: number): string => { + if (change === undefined) return "N/A"; + const symbol = change >= 0 ? "šŸ“ˆ" : "šŸ“‰"; + return `${symbol} ${Math.abs(change).toFixed(2)}%`; +}; + +export const shortenAddress = (address: string): string => { + if (!address || address.length <= 12) return address || "Unknown"; + return `${address.slice(0, 6)}...${address.slice(-4)}`; +}; + +export const formatTimestamp = (timestamp: number): string => { + return new Date(timestamp * 1000).toLocaleString(); +}; + +export const formatPrice = (price: number): string => { + return price < 0.01 ? price.toExponential(2) : price.toFixed(2); +}; + +// API helpers +export async function makeApiRequest( + url: string, + options: { + apiKey: string; + chain: Chain; + method?: "GET" | "POST"; + body?: any; + } +): Promise { + const { apiKey, chain, method = "GET", body } = options; + + try { + const response = await fetch(url, { + method, + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + ...(body && { "Content-Type": "application/json" }), + }, + ...(body && { body: JSON.stringify(body) }), + }); + + if (!response.ok) { + if (response.status === 404) { + throw new BirdeyeApiError(404, "Resource not found"); + } + if (response.status === 429) { + throw new BirdeyeApiError(429, "Rate limit exceeded"); + } + throw new BirdeyeApiError( + response.status, + `HTTP error! status: ${response.status}` + ); + } + + const data: ApiResponse = await response.json(); + + if (!data.success) { + throw new Error(data.error || "Unknown API error"); + } + + return data.data; + } catch (error) { + if (error instanceof BirdeyeApiError) { + elizaLogger.error(`API Error (${error.status}):`, error.message); + } else { + elizaLogger.error("Error making API request:", error); + } + throw error; + } +} diff --git a/packages/plugin-birdeye/src/providers/wallet/index.ts b/packages/plugin-birdeye/src/providers/wallet/index.ts new file mode 100644 index 0000000000..7cc0f1aa25 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/wallet/index.ts @@ -0,0 +1,6 @@ +export * from "./portfolio-multichain-provider"; +export * from "./supported-networks-provider"; +export * from "./token-balance-provider"; +export * from "./transaction-history-multichain-provider"; +export * from "./transaction-history-provider"; +export * from "./wallet-portfolio-provider"; diff --git a/packages/plugin-birdeye/src/providers/wallet/portfolio-multichain-provider.ts b/packages/plugin-birdeye/src/providers/wallet/portfolio-multichain-provider.ts new file mode 100644 index 0000000000..b9058ac7e3 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/wallet/portfolio-multichain-provider.ts @@ -0,0 +1,159 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { BASE_URL, Chain, makeApiRequest } from "../utils"; + +// Types +interface TokenHolding { + chain: Chain; + tokenAddress: string; + symbol: string; + name: string; + balance: number; + price: number; + value: number; + priceChange24h: number; +} + +interface MultichainPortfolioResponse { + holdings: TokenHolding[]; + totalValue: number; + valueChange24h: number; +} + +// Constants +const MULTICHAIN_PORTFOLIO_KEYWORDS = [ + "multichain portfolio", + "cross chain portfolio", + "all chain portfolio", + "portfolio across chains", + "portfolio on all chains", +] as const; + +// Helper functions +const containsMultichainPortfolioKeyword = (text: string): boolean => { + return MULTICHAIN_PORTFOLIO_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const extractWalletAddress = (text: string): string | null => { + // Look for wallet address patterns + const addressMatch = text.match(/\b[1-9A-HJ-NP-Za-km-z]{32,44}\b/); + return addressMatch ? addressMatch[0] : null; +}; + +const getMultichainPortfolio = async ( + apiKey: string, + walletAddress: string +): Promise => { + try { + const url = `${BASE_URL}/wallet/portfolio_multichain`; + + elizaLogger.info("Fetching multichain portfolio from:", url); + + return await makeApiRequest(url, { + apiKey, + chain: "solana", + body: { wallet: walletAddress }, + }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error( + "Error fetching multichain portfolio:", + error.message + ); + } + return null; + } +}; + +const formatMultichainPortfolioResponse = ( + data: MultichainPortfolioResponse +): string => { + let response = "šŸŒ Multichain Portfolio Overview\n\n"; + + // Add total portfolio value and 24h change + const valueChangePercent = (data.valueChange24h * 100).toFixed(2); + const valueChangeEmoji = data.valueChange24h >= 0 ? "šŸ“ˆ" : "šŸ“‰"; + + response += `Total Portfolio Value: $${data.totalValue.toLocaleString()}\n`; + response += `24h Change: ${valueChangePercent}% ${valueChangeEmoji}\n\n`; + + // Group holdings by chain + const holdingsByChain = data.holdings.reduce( + (acc, holding) => { + if (!acc[holding.chain]) { + acc[holding.chain] = []; + } + acc[holding.chain].push(holding); + return acc; + }, + {} as Record + ); + + // Format holdings by chain + Object.entries(holdingsByChain).forEach(([chain, holdings]) => { + response += `${chain.toUpperCase()} Holdings\n`; + + // Sort holdings by value + holdings.sort((a, b) => b.value - a.value); + + holdings.forEach((holding) => { + const priceChangePercent = (holding.priceChange24h * 100).toFixed( + 2 + ); + const priceChangeEmoji = holding.priceChange24h >= 0 ? "šŸ“ˆ" : "šŸ“‰"; + + response += `ā€¢ ${holding.name} (${holding.symbol})\n`; + response += ` - Balance: ${holding.balance.toLocaleString()}\n`; + response += ` - Price: $${holding.price.toFixed(6)}\n`; + response += ` - Value: $${holding.value.toLocaleString()}\n`; + response += ` - 24h Change: ${priceChangePercent}% ${priceChangeEmoji}\n\n`; + }); + }); + + return response.trim(); +}; + +export const portfolioMultichainProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsMultichainPortfolioKeyword(messageText)) { + return null; + } + + const walletAddress = extractWalletAddress(messageText); + if (!walletAddress) { + return "Please provide a valid wallet address to check the multichain portfolio."; + } + + elizaLogger.info("PORTFOLIO_MULTICHAIN provider activated"); + + const portfolioData = await getMultichainPortfolio( + apiKey, + walletAddress + ); + + if (!portfolioData) { + return null; + } + + return formatMultichainPortfolioResponse(portfolioData); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/wallet/supported-networks-provider.ts b/packages/plugin-birdeye/src/providers/wallet/supported-networks-provider.ts new file mode 100644 index 0000000000..26b6ca6bbb --- /dev/null +++ b/packages/plugin-birdeye/src/providers/wallet/supported-networks-provider.ts @@ -0,0 +1,131 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { BASE_URL, Chain, makeApiRequest } from "../utils"; + +// Types +interface NetworkSupport { + chain: Chain; + status: "active" | "maintenance" | "deprecated"; + features: string[]; +} + +interface SupportedNetworksResponse { + networks: NetworkSupport[]; +} + +// Constants +const SUPPORTED_NETWORKS_KEYWORDS = [ + "supported wallet networks", + "wallet networks", + "wallet chains", + "supported wallet chains", + "wallet network support", +] as const; + +// Helper functions +const containsSupportedNetworksKeyword = (text: string): boolean => { + return SUPPORTED_NETWORKS_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getSupportedNetworks = async ( + apiKey: string +): Promise => { + try { + const url = `${BASE_URL}/wallet/supported_networks`; + + elizaLogger.info("Fetching supported wallet networks from:", url); + + return await makeApiRequest(url, { + apiKey, + chain: "solana", + }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error( + "Error fetching supported networks:", + error.message + ); + } + return null; + } +}; + +const formatSupportedNetworksResponse = ( + data: SupportedNetworksResponse +): string => { + let response = "šŸŒ Supported Wallet Networks\n\n"; + + // Group networks by status + const activeNetworks = data.networks.filter((n) => n.status === "active"); + const maintenanceNetworks = data.networks.filter( + (n) => n.status === "maintenance" + ); + const deprecatedNetworks = data.networks.filter( + (n) => n.status === "deprecated" + ); + + // Format active networks + if (activeNetworks.length > 0) { + response += "šŸŸ¢ Active Networks\n"; + activeNetworks.forEach((network) => { + response += `ā€¢ ${network.chain}\n`; + response += ` - Features: ${network.features.join(", ")}\n\n`; + }); + } + + // Format maintenance networks + if (maintenanceNetworks.length > 0) { + response += "šŸŸ” Networks Under Maintenance\n"; + maintenanceNetworks.forEach((network) => { + response += `ā€¢ ${network.chain}\n`; + response += ` - Features: ${network.features.join(", ")}\n\n`; + }); + } + + // Format deprecated networks + if (deprecatedNetworks.length > 0) { + response += "šŸ”“ Deprecated Networks\n"; + deprecatedNetworks.forEach((network) => { + response += `ā€¢ ${network.chain}\n\n`; + }); + } + + return response.trim(); +}; + +export const supportedNetworksProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsSupportedNetworksKeyword(messageText)) { + return null; + } + + elizaLogger.info("SUPPORTED_NETWORKS provider activated"); + + const networksData = await getSupportedNetworks(apiKey); + + if (!networksData) { + return null; + } + + return formatSupportedNetworksResponse(networksData); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/wallet/token-balance-provider.ts b/packages/plugin-birdeye/src/providers/wallet/token-balance-provider.ts new file mode 100644 index 0000000000..6e82646c45 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/wallet/token-balance-provider.ts @@ -0,0 +1,135 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { BASE_URL, Chain, makeApiRequest } from "../utils"; + +// Types +interface TokenBalance { + tokenAddress: string; + symbol: string; + name: string; + balance: number; + decimals: number; + price: number; + value: number; + priceChange24h: number; +} + +interface TokenBalanceResponse { + balances: TokenBalance[]; + totalValue: number; + valueChange24h: number; +} + +// Constants +const TOKEN_BALANCE_KEYWORDS = [ + "token balance", + "token holdings", + "wallet balance", + "wallet holdings", + "check balance", + "check holdings", +] as const; + +// Helper functions +const containsTokenBalanceKeyword = (text: string): boolean => { + return TOKEN_BALANCE_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const extractWalletAddress = (text: string): string | null => { + // Look for wallet address patterns + const addressMatch = text.match(/\b[1-9A-HJ-NP-Za-km-z]{32,44}\b/); + return addressMatch ? addressMatch[0] : null; +}; + +const getTokenBalance = async ( + apiKey: string, + walletAddress: string, + chain: Chain = "solana" +): Promise => { + try { + const url = `${BASE_URL}/wallet/token_balance`; + + elizaLogger.info("Fetching token balance from:", url); + + return await makeApiRequest(url, { + apiKey, + chain, + body: { wallet: walletAddress }, + }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching token balance:", error.message); + } + return null; + } +}; + +const formatTokenBalanceResponse = (data: TokenBalanceResponse): string => { + let response = "šŸ’° Token Balance Overview\n\n"; + + // Add total value and 24h change + const valueChangePercent = (data.valueChange24h * 100).toFixed(2); + const valueChangeEmoji = data.valueChange24h >= 0 ? "šŸ“ˆ" : "šŸ“‰"; + + response += `Total Value: $${data.totalValue.toLocaleString()}\n`; + response += `24h Change: ${valueChangePercent}% ${valueChangeEmoji}\n\n`; + + // Sort balances by value + const sortedBalances = [...data.balances].sort((a, b) => b.value - a.value); + + // Format individual token balances + sortedBalances.forEach((balance) => { + const priceChangePercent = (balance.priceChange24h * 100).toFixed(2); + const priceChangeEmoji = balance.priceChange24h >= 0 ? "šŸ“ˆ" : "šŸ“‰"; + + response += `${balance.name} (${balance.symbol})\n`; + response += `ā€¢ Balance: ${balance.balance.toLocaleString()}\n`; + response += `ā€¢ Price: $${balance.price.toFixed(6)}\n`; + response += `ā€¢ Value: $${balance.value.toLocaleString()}\n`; + response += `ā€¢ 24h Change: ${priceChangePercent}% ${priceChangeEmoji}\n\n`; + }); + + return response.trim(); +}; + +export const tokenBalanceProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsTokenBalanceKeyword(messageText)) { + return null; + } + + const walletAddress = extractWalletAddress(messageText); + if (!walletAddress) { + return "Please provide a valid wallet address to check the token balance."; + } + + elizaLogger.info("TOKEN_BALANCE provider activated"); + + const balanceData = await getTokenBalance(apiKey, walletAddress); + + if (!balanceData) { + return null; + } + + return formatTokenBalanceResponse(balanceData); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/wallet/transaction-history-multichain-provider.ts b/packages/plugin-birdeye/src/providers/wallet/transaction-history-multichain-provider.ts new file mode 100644 index 0000000000..66a6c8cb3f --- /dev/null +++ b/packages/plugin-birdeye/src/providers/wallet/transaction-history-multichain-provider.ts @@ -0,0 +1,174 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; +import { BASE_URL, Chain, makeApiRequest } from "../utils"; + +// Types +interface Transaction { + chain: Chain; + hash: string; + timestamp: number; + type: string; + status: "success" | "failed" | "pending"; + value: number; + fee: number; + from: string; + to: string; + tokenTransfers?: { + token: string; + amount: number; + value: number; + }[]; +} + +interface TransactionHistoryResponse { + transactions: Transaction[]; +} + +// Constants +const MULTICHAIN_HISTORY_KEYWORDS = [ + "multichain transactions", + "cross chain transactions", + "all chain transactions", + "transactions across chains", + "transaction history all chains", +] as const; + +// Helper functions +const containsMultichainHistoryKeyword = (text: string): boolean => { + return MULTICHAIN_HISTORY_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const extractWalletAddress = (text: string): string | null => { + // Look for wallet address patterns + const addressMatch = text.match(/\b[1-9A-HJ-NP-Za-km-z]{32,44}\b/); + return addressMatch ? addressMatch[0] : null; +}; + +const getTransactionHistory = async ( + apiKey: string, + walletAddress: string +): Promise => { + try { + const url = `${BASE_URL}/wallet/transaction_history_multichain`; + + elizaLogger.info("Fetching multichain transaction history from:", url); + + return await makeApiRequest(url, { + apiKey, + chain: "solana", + body: { wallet: walletAddress }, + }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error( + "Error fetching transaction history:", + error.message + ); + } + return null; + } +}; + +const formatTransactionStatus = (status: Transaction["status"]): string => { + switch (status) { + case "success": + return "āœ…"; + case "failed": + return "āŒ"; + case "pending": + return "ā³"; + default: + return "ā“"; + } +}; + +const formatTransactionHistoryResponse = ( + data: TransactionHistoryResponse +): string => { + let response = "šŸ“œ Multichain Transaction History\n\n"; + + // Group transactions by chain + const txsByChain = data.transactions.reduce( + (acc, tx) => { + if (!acc[tx.chain]) { + acc[tx.chain] = []; + } + acc[tx.chain].push(tx); + return acc; + }, + {} as Record + ); + + // Format transactions by chain + Object.entries(txsByChain).forEach(([chain, transactions]) => { + response += `${chain.toUpperCase()} Transactions\n`; + + // Sort transactions by timestamp (newest first) + transactions.sort((a, b) => b.timestamp - a.timestamp); + + transactions.forEach((tx) => { + const date = new Date(tx.timestamp * 1000).toLocaleString(); + const statusEmoji = formatTransactionStatus(tx.status); + + response += `${statusEmoji} ${tx.type} - ${date}\n`; + response += `ā€¢ Hash: ${tx.hash}\n`; + response += `ā€¢ Value: $${tx.value.toLocaleString()}\n`; + response += `ā€¢ Fee: $${tx.fee.toFixed(6)}\n`; + response += `ā€¢ From: ${tx.from}\n`; + response += `ā€¢ To: ${tx.to}\n`; + + if (tx.tokenTransfers && tx.tokenTransfers.length > 0) { + response += "ā€¢ Token Transfers:\n"; + tx.tokenTransfers.forEach((transfer) => { + response += ` - ${transfer.token}: ${transfer.amount} ($${transfer.value.toLocaleString()})\n`; + }); + } + + response += "\n"; + }); + }); + + return response.trim(); +}; + +export const transactionHistoryMultichainProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsMultichainHistoryKeyword(messageText)) { + return null; + } + + const walletAddress = extractWalletAddress(messageText); + if (!walletAddress) { + return "Please provide a valid wallet address to check the transaction history."; + } + + elizaLogger.info("TRANSACTION_HISTORY_MULTICHAIN provider activated"); + + const historyData = await getTransactionHistory(apiKey, walletAddress); + + if (!historyData) { + return null; + } + + return formatTransactionHistoryResponse(historyData); + }, +}; diff --git a/packages/plugin-birdeye/src/providers/wallet/transaction-history-provider.ts b/packages/plugin-birdeye/src/providers/wallet/transaction-history-provider.ts new file mode 100644 index 0000000000..a303e0fa10 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/wallet/transaction-history-provider.ts @@ -0,0 +1,381 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; + +// Types +interface TokenTransfer { + token: string; + symbol: string; + amount: number; + value: number; + decimals: number; +} + +interface Transaction { + hash: string; + timestamp: number; + type: "send" | "receive" | "swap" | "mint" | "burn" | "other"; + from: string; + to: string; + value: number; + fee: number; + success: boolean; + transfers: TokenTransfer[]; +} + +interface TransactionHistory { + transactions: Transaction[]; + totalCount: number; +} + +interface MultichainTransactionHistory { + [chain: string]: TransactionHistory; +} + +// Constants +const TRANSACTION_KEYWORDS = [ + "transaction", + "transactions", + "history", + "transfers", + "activity", + "trades", + "swaps", + "sent", + "received", + "tx", + "txs", +] as const; + +const MULTICHAIN_KEYWORDS = [ + "all chains", + "multichain", + "multi-chain", + "cross chain", + "cross-chain", + "every chain", + "all networks", +] as const; + +const CHAIN_KEYWORDS = [ + "solana", + "ethereum", + "arbitrum", + "avalanche", + "bsc", + "optimism", + "polygon", + "base", + "zksync", + "sui", +] as const; + +const BASE_URL = "https://public-api.birdeye.so"; + +// Helper functions +const containsTransactionKeyword = (text: string): boolean => { + return TRANSACTION_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const isMultichainRequest = (text: string): boolean => { + return MULTICHAIN_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const extractChain = (text: string): string => { + const chain = CHAIN_KEYWORDS.find((chain) => + text.toLowerCase().includes(chain.toLowerCase()) + ); + return chain || "solana"; +}; + +const extractWalletAddress = (text: string): string | null => { + const words = text.split(/\s+/); + + for (const word of words) { + // Ethereum-like addresses (0x...) + if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { + return word; + } + // Solana addresses (base58, typically 32-44 chars) + if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { + return word; + } + } + return null; +}; + +const getTransactionHistory = async ( + apiKey: string, + walletAddress: string, + chain: string = "solana", + limit: number = 10 +): Promise => { + try { + const params = new URLSearchParams({ + wallet: walletAddress, + limit: limit.toString(), + }); + const url = `${BASE_URL}/wallet/transaction_history?${params.toString()}`; + + elizaLogger.info( + `Fetching transaction history for wallet ${walletAddress} on ${chain} from:`, + url + ); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + }, + }); + + if (!response.ok) { + if (response.status === 404) { + elizaLogger.warn( + `Wallet not found: ${walletAddress} on ${chain}` + ); + return null; + } + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.data; + } catch (error) { + elizaLogger.error("Error fetching transaction history:", error); + return null; + } +}; + +const getMultichainTransactionHistory = async ( + apiKey: string, + walletAddress: string, + limit: number = 10 +): Promise => { + try { + const params = new URLSearchParams({ + wallet: walletAddress, + limit: limit.toString(), + }); + const url = `${BASE_URL}/wallet/transaction_history_multichain?${params.toString()}`; + + elizaLogger.info( + `Fetching multichain transaction history for wallet ${walletAddress} from:`, + url + ); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + }, + }); + + if (!response.ok) { + if (response.status === 404) { + elizaLogger.warn(`Wallet not found: ${walletAddress}`); + return null; + } + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.data; + } catch (error) { + elizaLogger.error( + "Error fetching multichain transaction history:", + error + ); + return null; + } +}; + +const formatValue = (value: number): string => { + if (value >= 1_000_000_000) { + return `$${(value / 1_000_000_000).toFixed(2)}B`; + } + if (value >= 1_000_000) { + return `$${(value / 1_000_000).toFixed(2)}M`; + } + if (value >= 1_000) { + return `$${(value / 1_000).toFixed(2)}K`; + } + return `$${value.toFixed(2)}`; +}; + +const formatTokenAmount = (amount: number, decimals: number): string => { + const formattedAmount = amount / Math.pow(10, decimals); + if (formattedAmount >= 1_000_000) { + return `${(formattedAmount / 1_000_000).toFixed(2)}M`; + } + if (formattedAmount >= 1_000) { + return `${(formattedAmount / 1_000).toFixed(2)}K`; + } + return formattedAmount.toFixed(decimals > 6 ? 4 : 2); +}; + +const shortenAddress = (address: string): string => { + if (address.length <= 12) return address; + return `${address.slice(0, 6)}...${address.slice(-4)}`; +}; + +const formatTransactionType = (type: string): string => { + switch (type.toLowerCase()) { + case "send": + return "šŸ“¤ Sent"; + case "receive": + return "šŸ“„ Received"; + case "swap": + return "šŸ”„ Swapped"; + case "mint": + return "šŸŒŸ Minted"; + case "burn": + return "šŸ”„ Burned"; + default: + return "šŸ“ Other"; + } +}; + +const formatSingleChainHistory = ( + history: TransactionHistory, + chain: string +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + let response = `Transaction History on ${chainName}:\n\n`; + + if (history.transactions.length === 0) { + return response + "No transactions found."; + } + + history.transactions.forEach((tx, index) => { + const date = new Date(tx.timestamp * 1000).toLocaleString(); + response += `${index + 1}. ${formatTransactionType(tx.type)} - ${date}\n`; + response += `ā€¢ Hash: ${shortenAddress(tx.hash)}\n`; + response += `ā€¢ From: ${shortenAddress(tx.from)}\n`; + response += `ā€¢ To: ${shortenAddress(tx.to)}\n`; + response += `ā€¢ Value: ${formatValue(tx.value)}\n`; + response += `ā€¢ Fee: ${formatValue(tx.fee)}\n`; + response += `ā€¢ Status: ${tx.success ? "āœ… Success" : "āŒ Failed"}\n`; + + if (tx.transfers.length > 0) { + response += "ā€¢ Tokens:\n"; + tx.transfers.forEach((transfer) => { + const amount = formatTokenAmount( + transfer.amount, + transfer.decimals + ); + response += ` - ${amount} ${transfer.symbol} (${formatValue(transfer.value)})\n`; + }); + } + response += "\n"; + }); + + if (history.totalCount > history.transactions.length) { + response += `\nShowing ${history.transactions.length} of ${history.totalCount} total transactions.`; + } + + return response; +}; + +const formatMultichainHistory = ( + history: MultichainTransactionHistory +): string => { + let response = `Multichain Transaction History:\n\n`; + + const chains = Object.keys(history); + if (chains.length === 0) { + return response + "No transactions found on any chain."; + } + + chains.forEach((chain) => { + const chainData = history[chain]; + if (chainData.transactions.length > 0) { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + response += `${chainName} (${chainData.totalCount} total transactions):\n`; + + chainData.transactions + .slice(0, 5) // Show only the 5 most recent transactions per chain + .forEach((tx, index) => { + const date = new Date(tx.timestamp * 1000).toLocaleString(); + response += `${index + 1}. ${formatTransactionType(tx.type)} - ${date}\n`; + response += ` Value: ${formatValue(tx.value)} | Status: ${tx.success ? "āœ…" : "āŒ"}\n`; + }); + + if (chainData.transactions.length > 5) { + response += ` ... and ${chainData.totalCount - 5} more transactions\n`; + } + response += "\n"; + } + }); + + return response; +}; + +export const transactionHistoryProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsTransactionKeyword(messageText)) { + return null; + } + + const walletAddress = extractWalletAddress(messageText); + if (!walletAddress) { + return null; + } + + const isMultichain = isMultichainRequest(messageText); + + if (isMultichain) { + elizaLogger.info( + `MULTICHAIN TRANSACTION HISTORY provider activated for wallet ${walletAddress}` + ); + + const historyData = await getMultichainTransactionHistory( + apiKey, + walletAddress + ); + + if (!historyData) { + return null; + } + + return formatMultichainHistory(historyData); + } else { + const chain = extractChain(messageText); + + elizaLogger.info( + `TRANSACTION HISTORY provider activated for wallet ${walletAddress} on ${chain}` + ); + + const historyData = await getTransactionHistory( + apiKey, + walletAddress, + chain + ); + + if (!historyData) { + return null; + } + + return formatSingleChainHistory(historyData, chain); + } + }, +}; diff --git a/packages/plugin-birdeye/src/providers/wallet/wallet-portfolio-provider.ts b/packages/plugin-birdeye/src/providers/wallet/wallet-portfolio-provider.ts new file mode 100644 index 0000000000..04adbfe98f --- /dev/null +++ b/packages/plugin-birdeye/src/providers/wallet/wallet-portfolio-provider.ts @@ -0,0 +1,335 @@ +import { + IAgentRuntime, + Memory, + Provider, + State, + elizaLogger, +} from "@elizaos/core"; + +// Types +interface TokenBalance { + token: string; + symbol: string; + amount: number; + price: number; + value: number; + decimals: number; + logoURI?: string; +} + +interface PortfolioData { + totalValue: number; + tokens: TokenBalance[]; + lastUpdated: number; +} + +interface MultichainPortfolioData { + chains: Record; + totalValue: number; +} + +// Constants +const PORTFOLIO_KEYWORDS = [ + "portfolio", + "holdings", + "balance", + "assets", + "tokens", + "wallet", + "what do i own", + "what do i have", +] as const; + +const MULTICHAIN_KEYWORDS = [ + "all chains", + "multichain", + "multi-chain", + "cross chain", + "cross-chain", + "every chain", + "all networks", +] as const; + +const CHAIN_KEYWORDS = [ + "solana", + "ethereum", + "arbitrum", + "avalanche", + "bsc", + "optimism", + "polygon", + "base", + "zksync", + "sui", +] as const; + +const BASE_URL = "https://public-api.birdeye.so"; + +// Helper functions +const containsPortfolioKeyword = (text: string): boolean => { + return PORTFOLIO_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const isMultichainRequest = (text: string): boolean => { + return MULTICHAIN_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const extractChain = (text: string): string => { + const chain = CHAIN_KEYWORDS.find((chain) => + text.toLowerCase().includes(chain.toLowerCase()) + ); + return chain || "solana"; +}; + +const extractWalletAddress = (text: string): string | null => { + const words = text.split(/\s+/); + + for (const word of words) { + // Ethereum-like addresses (0x...) + if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { + return word; + } + // Solana addresses (base58, typically 32-44 chars) + if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { + return word; + } + } + return null; +}; + +const getWalletPortfolio = async ( + apiKey: string, + walletAddress: string, + chain: string = "solana" +): Promise => { + try { + const params = new URLSearchParams({ + wallet: walletAddress, + }); + const url = `${BASE_URL}/wallet/portfolio?${params.toString()}`; + + elizaLogger.info( + `Fetching portfolio for wallet ${walletAddress} on ${chain} from:`, + url + ); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + }, + }); + + if (!response.ok) { + if (response.status === 404) { + elizaLogger.warn( + `Wallet not found: ${walletAddress} on ${chain}` + ); + return null; + } + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data.data; + } catch (error) { + elizaLogger.error("Error fetching wallet portfolio:", error); + return null; + } +}; + +const getMultichainPortfolio = async ( + apiKey: string, + walletAddress: string +): Promise => { + try { + const params = new URLSearchParams({ + wallet: walletAddress, + }); + const url = `${BASE_URL}/wallet/portfolio_multichain?${params.toString()}`; + + elizaLogger.info( + `Fetching multichain portfolio for wallet ${walletAddress} from:`, + url + ); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + }, + }); + + if (!response.ok) { + if (response.status === 404) { + elizaLogger.warn(`Wallet not found: ${walletAddress}`); + return null; + } + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + // Transform the response to match our interface + const { totalValue, ...chains } = data.data; + return { + chains, + totalValue, + }; + } catch (error) { + elizaLogger.error("Error fetching multichain portfolio:", error); + return null; + } +}; + +const formatValue = (value: number): string => { + if (value >= 1_000_000_000) { + return `$${(value / 1_000_000_000).toFixed(2)}B`; + } + if (value >= 1_000_000) { + return `$${(value / 1_000_000).toFixed(2)}M`; + } + if (value >= 1_000) { + return `$${(value / 1_000).toFixed(2)}K`; + } + return `$${value.toFixed(2)}`; +}; + +const formatTokenAmount = (amount: number, decimals: number): string => { + const formattedAmount = amount / Math.pow(10, decimals); + if (formattedAmount >= 1_000_000) { + return `${(formattedAmount / 1_000_000).toFixed(2)}M`; + } + if (formattedAmount >= 1_000) { + return `${(formattedAmount / 1_000).toFixed(2)}K`; + } + return formattedAmount.toFixed(decimals > 6 ? 4 : 2); +}; + +const formatSingleChainPortfolio = ( + data: PortfolioData, + chain: string +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + let response = `Portfolio on ${chainName}:\n\n`; + + response += `šŸ’° Total Value: ${formatValue(data.totalValue)}\n\n`; + + if (data.tokens.length === 0) { + response += "No tokens found in this wallet."; + return response; + } + + response += `Token Holdings:\n`; + data.tokens + .sort((a, b) => b.value - a.value) + .forEach((token) => { + const amount = formatTokenAmount(token.amount, token.decimals); + response += `ā€¢ ${token.symbol}: ${amount} (${formatValue(token.value)})\n`; + }); + + response += `\nLast Updated: ${new Date(data.lastUpdated * 1000).toLocaleString()}`; + return response; +}; + +const formatMultichainPortfolio = (data: MultichainPortfolioData): string => { + let response = `Multichain Portfolio Overview:\n\n`; + response += `šŸ’° Total Portfolio Value: ${formatValue(data.totalValue)}\n\n`; + + const chains = Object.keys(data.chains); + if (chains.length === 0) { + response += "No assets found across any chains."; + return response; + } + + chains + .sort((a, b) => data.chains[b].totalValue - data.chains[a].totalValue) + .forEach((chain) => { + const chainData = data.chains[chain]; + if (chainData.totalValue > 0) { + const chainName = + chain.charAt(0).toUpperCase() + chain.slice(1); + response += `${chainName} (${formatValue(chainData.totalValue)}):\n`; + chainData.tokens + .sort((a, b) => b.value - a.value) + .slice(0, 5) // Show top 5 tokens per chain + .forEach((token) => { + const amount = formatTokenAmount( + token.amount, + token.decimals + ); + response += `ā€¢ ${token.symbol}: ${amount} (${formatValue(token.value)})\n`; + }); + if (chainData.tokens.length > 5) { + response += ` ... and ${chainData.tokens.length - 5} more tokens\n`; + } + response += "\n"; + } + }); + + return response; +}; + +export const walletPortfolioProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + return null; + } + + const messageText = message.content.text; + + if (!containsPortfolioKeyword(messageText)) { + return null; + } + + const walletAddress = extractWalletAddress(messageText); + if (!walletAddress) { + return null; + } + + const isMultichain = isMultichainRequest(messageText); + + if (isMultichain) { + elizaLogger.info( + `MULTICHAIN PORTFOLIO provider activated for wallet ${walletAddress}` + ); + + const portfolioData = await getMultichainPortfolio( + apiKey, + walletAddress + ); + + if (!portfolioData) { + return null; + } + + return formatMultichainPortfolio(portfolioData); + } else { + const chain = extractChain(messageText); + + elizaLogger.info( + `PORTFOLIO provider activated for wallet ${walletAddress} on ${chain}` + ); + + const portfolioData = await getWalletPortfolio( + apiKey, + walletAddress, + chain + ); + + if (!portfolioData) { + return null; + } + + return formatSingleChainPortfolio(portfolioData, chain); + } + }, +}; diff --git a/packages/plugin-birdeye/tsconfig.json b/packages/plugin-birdeye/tsconfig.json new file mode 100644 index 0000000000..73993deaaf --- /dev/null +++ b/packages/plugin-birdeye/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../core/tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": [ + "src/**/*.ts" + ] +} \ No newline at end of file diff --git a/packages/plugin-birdeye/tsup.config.ts b/packages/plugin-birdeye/tsup.config.ts new file mode 100644 index 0000000000..dd25475bb6 --- /dev/null +++ b/packages/plugin-birdeye/tsup.config.ts @@ -0,0 +1,29 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + outDir: "dist", + sourcemap: true, + clean: true, + format: ["esm"], // Ensure you're targeting CommonJS + external: [ + "dotenv", // Externalize dotenv to prevent bundling + "fs", // Externalize fs to use Node.js built-in module + "path", // Externalize other built-ins if necessary + "@reflink/reflink", + "@node-llama-cpp", + "https", + "http", + "agentkeepalive", + "safe-buffer", + "base-x", + "bs58", + "borsh", + "@solana/buffer-layout", + "stream", + "buffer", + "querystring", + "amqplib", + // Add other modules you want to externalize + ], +}); From 176a0e76572529a57678fd125ec6b437e059aa1f Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Thu, 26 Dec 2024 15:24:38 -0800 Subject: [PATCH 02/33] feat: got metadata endpoint working --- .env | 1 - agent/package.json | 3 +- agent/src/index.ts | 8 +- client/src/Chat.tsx | 12 +- packages/core/src/defaultCharacter.ts | 2 +- .../src/actions/defi/get-ohlcv.ts | 387 +++++++++++++++ .../src/actions/defi/get-price-history.ts | 434 +++++++++++++++++ .../actions/defi/get-supported-networks.ts | 147 ++++++ .../src/actions/defi/get-token-metadata.ts | 393 +++++++++++++++ .../src/actions/defi/get-token-trades.ts | 446 ++++++++++++++++++ packages/plugin-birdeye/src/index.ts | 81 ++-- .../src/providers/defi/networks-provider.ts | 105 +---- .../plugin-birdeye/src/providers/index.ts | 148 ------ .../plugin-birdeye/src/providers/utils.ts | 25 +- pnpm-lock.yaml | 48 ++ scripts/dev.sh | 2 +- 16 files changed, 1945 insertions(+), 297 deletions(-) delete mode 100644 .env create mode 100644 packages/plugin-birdeye/src/actions/defi/get-ohlcv.ts create mode 100644 packages/plugin-birdeye/src/actions/defi/get-price-history.ts create mode 100644 packages/plugin-birdeye/src/actions/defi/get-supported-networks.ts create mode 100644 packages/plugin-birdeye/src/actions/defi/get-token-metadata.ts create mode 100644 packages/plugin-birdeye/src/actions/defi/get-token-trades.ts delete mode 100644 packages/plugin-birdeye/src/providers/index.ts diff --git a/.env b/.env deleted file mode 100644 index 2be7c65ae9..0000000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -# hello world diff --git a/agent/package.json b/agent/package.json index be8a3e0e29..faed1c3efd 100644 --- a/agent/package.json +++ b/agent/package.json @@ -33,6 +33,7 @@ "@elizaos/plugin-abstract": "workspace:*", "@elizaos/plugin-aptos": "workspace:*", "@elizaos/plugin-bootstrap": "workspace:*", + "@elizaos/plugin-birdeye": "workspace:*", "@elizaos/plugin-intiface": "workspace:*", "@elizaos/plugin-coinbase": "workspace:*", "@elizaos/plugin-conflux": "workspace:*", @@ -60,4 +61,4 @@ "ts-node": "10.9.2", "tsup": "8.3.5" } -} +} \ No newline at end of file diff --git a/agent/src/index.ts b/agent/src/index.ts index 46b5bc622a..bc74b22730 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -32,6 +32,7 @@ import { bootstrapPlugin } from "@elizaos/plugin-bootstrap"; import createGoatPlugin from "@elizaos/plugin-goat"; // import { intifacePlugin } from "@elizaos/plugin-intiface"; import { DirectClient } from "@elizaos/client-direct"; +import { abstractPlugin } from "@elizaos/plugin-abstract"; import { aptosPlugin } from "@elizaos/plugin-aptos"; import { birdeyePlugin } from "@elizaos/plugin-birdeye"; import { @@ -56,13 +57,12 @@ import { suiPlugin } from "@elizaos/plugin-sui"; import { TEEMode, teePlugin } from "@elizaos/plugin-tee"; import { tonPlugin } from "@elizaos/plugin-ton"; import { zksyncEraPlugin } from "@elizaos/plugin-zksync-era"; -import { abstractPlugin } from "@elizaos/plugin-abstract"; import Database from "better-sqlite3"; import fs from "fs"; +import net from "net"; import path from "path"; import { fileURLToPath } from "url"; import yargs from "yargs"; -import net from "net"; const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file const __dirname = path.dirname(__filename); // get the name of the directory @@ -482,9 +482,7 @@ export async function createAgent( ? confluxPlugin : null, nodePlugin, - getSecret(character, "BIRDEYE_API_KEY") - ? birdeyePlugin - : null, + getSecret(character, "BIRDEYE_API_KEY") ? birdeyePlugin : null, getSecret(character, "SOLANA_PUBLIC_KEY") || (getSecret(character, "WALLET_PUBLIC_KEY") && !getSecret(character, "WALLET_PUBLIC_KEY")?.startsWith("0x")) diff --git a/client/src/Chat.tsx b/client/src/Chat.tsx index b32cc0b83e..f077935167 100644 --- a/client/src/Chat.tsx +++ b/client/src/Chat.tsx @@ -1,8 +1,8 @@ +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { useMutation } from "@tanstack/react-query"; import { useState } from "react"; import { useParams } from "react-router-dom"; -import { useMutation } from "@tanstack/react-query"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; import "./App.css"; type TextResponse = { @@ -58,7 +58,7 @@ export default function Chat() { messages.map((message, index) => (
- {message.text} +
+                                        {message.text}
+                                    
)) diff --git a/packages/core/src/defaultCharacter.ts b/packages/core/src/defaultCharacter.ts index 91cdeba925..17dd8860de 100644 --- a/packages/core/src/defaultCharacter.ts +++ b/packages/core/src/defaultCharacter.ts @@ -5,7 +5,7 @@ export const defaultCharacter: Character = { username: "eliza", plugins: [], clients: [], - modelProvider: ModelProviderName.LLAMALOCAL, + modelProvider: ModelProviderName.ANTHROPIC, settings: { secrets: {}, voice: { diff --git a/packages/plugin-birdeye/src/actions/defi/get-ohlcv.ts b/packages/plugin-birdeye/src/actions/defi/get-ohlcv.ts new file mode 100644 index 0000000000..ea977ea98d --- /dev/null +++ b/packages/plugin-birdeye/src/actions/defi/get-ohlcv.ts @@ -0,0 +1,387 @@ +import { + Action, + ActionExample, + Content, + elizaLogger, + Handler, + HandlerCallback, + IAgentRuntime, + Memory, + State, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + extractChain, + extractContractAddresses, + formatTimestamp, + formatValue, + makeApiRequest, +} from "../../providers/utils"; +import { getTokenMetadata, TokenMetadataResponse } from "./get-token-metadata"; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const exampleResponse = { + success: true, + data: { + items: [ + { + o: 128.27328370924414, + h: 128.6281001340782, + l: 127.91200927364626, + c: 127.97284640184616, + v: 58641.16636665621, + unixTime: 1726670700, + address: "So11111111111111111111111111111111111111112", + type: "15m", + }, + ], + }, +}; + +type OHLCVResponse = typeof exampleResponse; + +type TimeInterval = + | "1m" + | "3m" + | "5m" + | "15m" + | "30m" + | "1H" + | "2H" + | "4H" + | "6H" + | "8H" + | "12H" + | "1D" + | "3D" + | "1W" + | "1M"; + +const TIME_INTERVALS: Record = { + "1m": ["1 minute", "1min", "1m"], + "3m": ["3 minutes", "3min", "3m"], + "5m": ["5 minutes", "5min", "5m"], + "15m": ["15 minutes", "15min", "15m"], + "30m": ["30 minutes", "30min", "30m"], + "1H": ["1 hour", "1hr", "1h"], + "2H": ["2 hours", "2hr", "2h"], + "4H": ["4 hours", "4hr", "4h"], + "6H": ["6 hours", "6hr", "6h"], + "8H": ["8 hours", "8hr", "8h"], + "12H": ["12 hours", "12hr", "12h"], + "1D": ["1 day", "daily", "1d"], + "3D": ["3 days", "3day", "3d"], + "1W": ["1 week", "weekly", "1w"], + "1M": ["1 month", "monthly", "1m"], +}; + +const DEFAULT_INTERVAL: TimeInterval = "1D"; + +// Constants for keyword matching +const OHLCV_KEYWORDS = [ + "ohlc", + "ohlcv", + "candlestick", + "candle", + "chart", + "price history", + "open close high low", + "opening price", + "closing price", + "historical", +] as const; + +// Helper function to check if text contains OHLCV-related keywords +const containsOHLCVKeyword = (text: string): boolean => { + return OHLCV_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const extractTimeInterval = (text: string): TimeInterval => { + const lowerText = text.toLowerCase(); + + // First try exact matches + for (const [interval, keywords] of Object.entries(TIME_INTERVALS)) { + if (keywords.some((keyword) => lowerText.includes(keyword))) { + return interval as TimeInterval; + } + } + + // Then try common variations + if (lowerText.includes("hourly")) return "1H"; + if (lowerText.includes("daily")) return "1D"; + if (lowerText.includes("weekly")) return "1W"; + if (lowerText.includes("monthly")) return "1M"; + + return DEFAULT_INTERVAL; +}; + +const formatVolume = (volume: number): string => { + if (volume >= 1_000_000_000) { + return `$${(volume / 1_000_000_000).toFixed(2)}B`; + } + if (volume >= 1_000_000) { + return `$${(volume / 1_000_000).toFixed(2)}M`; + } + if (volume >= 1_000) { + return `$${(volume / 1_000).toFixed(2)}K`; + } + return `$${volume.toFixed(2)}`; +}; + +const getOHLCVData = async ( + apiKey: string, + contractAddress: string, + chain: Chain, + interval: TimeInterval = DEFAULT_INTERVAL +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + interval: interval.toLowerCase(), + limit: "24", // Get last 24 periods + }); + const url = `${BASE_URL}/defi/ohlcv?${params.toString()}`; + + elizaLogger.info( + `Fetching OHLCV data for ${contractAddress} on ${chain} with interval ${interval} from:`, + url + ); + + return await makeApiRequest(url, { apiKey, chain }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching OHLCV data:", error.message); + } + return null; + } +}; + +const formatOHLCVResponse = ( + data: OHLCVResponse, + tokenMetadata: TokenMetadataResponse | null, + chain: Chain, + interval: TimeInterval +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + + let tokenInfo = "Unknown Token"; + let tokenLinks = ""; + + if (tokenMetadata?.success) { + const { name, symbol, extensions } = tokenMetadata.data; + tokenInfo = `${name} (${symbol})`; + + const links = []; + if (extensions.website) links.push(`[Website](${extensions.website})`); + if (extensions.coingecko_id) + links.push( + `[CoinGecko](https://www.coingecko.com/en/coins/${extensions.coingecko_id})` + ); + if (links.length > 0) { + tokenLinks = `\nšŸ“Œ More Information: ${links.join(" ā€¢ ")}`; + } + } + + let response = `OHLCV Data for ${tokenInfo} on ${chainName}${tokenLinks}\n`; + response += `Interval: ${TIME_INTERVALS[interval][0]}\n\n`; + + if (!data.success || !data.data.items || data.data.items.length === 0) { + return response + "No OHLCV data available for the specified period."; + } + + const candles = data.data.items; + const latestCandle = candles[candles.length - 1]; + + // Latest candle information + response += `šŸ“Š Latest Candle (${formatTimestamp(latestCandle.unixTime)})\n`; + response += `ā€¢ Open: ${formatValue(latestCandle.o)}\n`; + response += `ā€¢ High: ${formatValue(latestCandle.h)}\n`; + response += `ā€¢ Low: ${formatValue(latestCandle.l)}\n`; + response += `ā€¢ Close: ${formatValue(latestCandle.c)}\n`; + response += `ā€¢ Volume: ${formatVolume(latestCandle.v)}\n\n`; + + // Price change statistics + const priceChange = latestCandle.c - latestCandle.o; + const priceChangePercent = (priceChange / latestCandle.o) * 100; + const trend = priceChange >= 0 ? "šŸ“ˆ" : "šŸ“‰"; + + response += `${trend} Period Change\n`; + response += `ā€¢ Price Change: ${formatValue(priceChange)} (${priceChangePercent.toFixed(2)}%)\n\n`; + + // Volume analysis + const totalVolume = candles.reduce((sum, candle) => sum + candle.v, 0); + const avgVolume = totalVolume / candles.length; + const highestVolume = Math.max(...candles.map((c) => c.v)); + const lowestVolume = Math.min(...candles.map((c) => c.v)); + + response += `šŸ“Š Volume Analysis\n`; + response += `ā€¢ Total Volume: ${formatVolume(totalVolume)}\n`; + response += `ā€¢ Average Volume: ${formatVolume(avgVolume)}\n`; + response += `ā€¢ Highest Volume: ${formatVolume(highestVolume)}\n`; + response += `ā€¢ Lowest Volume: ${formatVolume(lowestVolume)}\n\n`; + + // Market analysis + const volatility = + ((Math.max(...candles.map((c) => c.h)) - + Math.min(...candles.map((c) => c.l))) / + avgVolume) * + 100; + const volumeLevel = + latestCandle.v > avgVolume * 1.5 + ? "high" + : latestCandle.v > avgVolume + ? "moderate" + : "low"; + const volatilityLevel = + volatility > 5 ? "high" : volatility > 2 ? "moderate" : "low"; + + response += `šŸ“ˆ Market Analysis\n`; + response += `ā€¢ Current volume is ${volumeLevel}\n`; + response += `ā€¢ Market volatility is ${volatilityLevel}\n`; + response += `ā€¢ Overall trend is ${priceChange >= 0 ? "upward" : "downward"} for this period\n`; + + return response; +}; + +export const getOHLCVAction: Action = { + name: "GET_OHLCV", + similes: [ + "SHOW_OHLCV", + "VIEW_CANDLESTICK", + "CHECK_PRICE_CHART", + "DISPLAY_OHLCV", + "GET_CANDLESTICK", + "SHOW_PRICE_CHART", + "VIEW_PRICE_HISTORY", + "CHECK_HISTORICAL_PRICES", + "PRICE_CANDLES", + "MARKET_CANDLES", + ], + description: + "Retrieve and analyze OHLCV (Open, High, Low, Close, Volume) data for a token, including price movements and volume analysis.", + validate: async ( + _runtime: IAgentRuntime, + message: Memory, + _state: State | undefined + ): Promise => { + return containsOHLCVKeyword(message.content.text); + }, + handler: (async ( + runtime: IAgentRuntime, + message: Memory, + _state: State | undefined, + _options: any, + callback: HandlerCallback + ): Promise => { + const callbackData: Content = { + text: "", + action: "GET_OHLCV_RESPONSE", + source: message.content.source, + }; + + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + callbackData.text = + "I'm unable to fetch the OHLCV data due to missing API credentials."; + await callback(callbackData); + return callbackData; + } + + const messageText = message.content.text; + const addresses = extractContractAddresses(messageText); + if (addresses.length === 0) { + callbackData.text = + "I couldn't find a valid token address in your message."; + await callback(callbackData); + return callbackData; + } + + const chain = extractChain(messageText); + const interval = extractTimeInterval(messageText); + + // First fetch token metadata + const tokenMetadata = await getTokenMetadata( + apiKey, + addresses[0], + chain + ); + + elizaLogger.info( + `OHLCV action activated for ${addresses[0]} on ${chain} with ${interval} interval` + ); + + const ohlcvData = await getOHLCVData( + apiKey, + addresses[0], + chain, + interval + ); + + if (!ohlcvData) { + callbackData.text = + "I apologize, but I couldn't retrieve the OHLCV data at the moment."; + await callback(callbackData); + return callbackData; + } + + callbackData.text = formatOHLCVResponse( + ohlcvData, + tokenMetadata, + chain, + interval + ); + await callback(callbackData); + return callbackData; + }) as Handler, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Show me the daily OHLCV data for token 0x1234... on Ethereum", + }, + }, + { + user: "{{user2}}", + content: { + text: "Here's the detailed OHLCV analysis including price movements, volume statistics, and market insights.", + action: "GET_OHLCV", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "What's the hourly candlestick data for ABC123... on Solana?", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll analyze the hourly OHLCV data and provide you with a comprehensive market overview.", + action: "GET_OHLCV", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Get me 15-minute candles for token XYZ... on BSC", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll fetch the 15-minute OHLCV data and provide detailed market analysis.", + action: "GET_OHLCV", + }, + }, + ], + ] as ActionExample[][], +}; diff --git a/packages/plugin-birdeye/src/actions/defi/get-price-history.ts b/packages/plugin-birdeye/src/actions/defi/get-price-history.ts new file mode 100644 index 0000000000..cc44b98f59 --- /dev/null +++ b/packages/plugin-birdeye/src/actions/defi/get-price-history.ts @@ -0,0 +1,434 @@ +import { + Action, + ActionExample, + Content, + elizaLogger, + Handler, + HandlerCallback, + IAgentRuntime, + Memory, + State, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + extractChain, + extractContractAddresses, + extractTimeRange, + formatTimestamp, + formatValue, + makeApiRequest, +} from "../../providers/utils"; +import { getTokenMetadata, TokenMetadataResponse } from "./get-token-metadata"; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const exampleResponse = { + success: true, + data: { + items: [ + { + unixTime: 1726670700, + value: 127.97284640184616, + }, + { + unixTime: 1726671600, + value: 128.04188346328968, + }, + { + unixTime: 1726672500, + value: 127.40223856228901, + }, + ], + }, +}; + +type PriceHistoryResponse = typeof exampleResponse; + +type TimeInterval = + | "1m" + | "3m" + | "5m" + | "15m" + | "30m" + | "1H" + | "2H" + | "4H" + | "6H" + | "8H" + | "12H" + | "1D" + | "3D" + | "1W" + | "1M"; + +const TIME_INTERVALS: Record = { + "1m": "1 minute", + "3m": "3 minutes", + "5m": "5 minutes", + "15m": "15 minutes", + "30m": "30 minutes", + "1H": "1 hour", + "2H": "2 hours", + "4H": "4 hours", + "6H": "6 hours", + "8H": "8 hours", + "12H": "12 hours", + "1D": "1 day", + "3D": "3 days", + "1W": "1 week", + "1M": "1 month", +}; + +const DEFAULT_INTERVAL: TimeInterval = "1D"; + +const extractTimeInterval = (text: string): TimeInterval => { + // First try to match exact interval codes + const intervalMatch = text.match( + /\b(1m|3m|5m|15m|30m|1H|2H|4H|6H|8H|12H|1D|3D|1W|1M)\b/i + ); + if (intervalMatch) { + return intervalMatch[1].toUpperCase() as TimeInterval; + } + + // Then try to match written intervals + const lowerText = text.toLowerCase(); + for (const [interval, description] of Object.entries(TIME_INTERVALS)) { + if (lowerText.includes(description.toLowerCase())) { + return interval as TimeInterval; + } + } + + // Common variations + if (lowerText.includes("hourly")) return "1H"; + if (lowerText.includes("daily")) return "1D"; + if (lowerText.includes("weekly")) return "1W"; + if (lowerText.includes("monthly")) return "1M"; + + return DEFAULT_INTERVAL; +}; + +// Constants for keyword matching +const PRICE_HISTORY_KEYWORDS = [ + "price history", + "historical price", + "price chart", + "price trend", + "price movement", + "price changes", + "price over time", + "price timeline", + "price performance", + "price data", + "historical data", + "price analysis", + "price tracking", + "price evolution", +] as const; + +// Helper function to check if text contains price history related keywords +const containsPriceHistoryKeyword = (text: string): boolean => { + return PRICE_HISTORY_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getPriceHistory = async ( + apiKey: string, + contractAddress: string, + startTime: number, + endTime: number, + chain: Chain, + interval: TimeInterval = DEFAULT_INTERVAL +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + time_from: startTime.toString(), + time_to: endTime.toString(), + interval: interval.toLowerCase(), + }); + const url = `${BASE_URL}/defi/price_history_unix?${params.toString()}`; + + elizaLogger.info( + `Fetching price history for token ${contractAddress} from ${new Date( + startTime * 1000 + ).toLocaleString()} to ${new Date( + endTime * 1000 + ).toLocaleString()} on ${chain} with ${TIME_INTERVALS[interval]} interval from:`, + url + ); + + return await makeApiRequest(url, { + apiKey, + chain, + }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching price history:", error.message); + } + return null; + } +}; + +const formatPriceHistoryResponse = ( + data: PriceHistoryResponse, + tokenMetadata: TokenMetadataResponse | null, + timeRange: { start: number; end: number }, + chain: Chain, + interval: TimeInterval +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + const startDate = formatTimestamp(timeRange.start); + const endDate = formatTimestamp(timeRange.end); + + let tokenInfo = "Unknown Token"; + let tokenLinks = ""; + + if (tokenMetadata?.success) { + const { name, symbol, extensions } = tokenMetadata.data; + tokenInfo = `${name} (${symbol})`; + + const links = []; + if (extensions.website) links.push(`[Website](${extensions.website})`); + if (extensions.coingecko_id) + links.push( + `[CoinGecko](https://www.coingecko.com/en/coins/${extensions.coingecko_id})` + ); + if (links.length > 0) { + tokenLinks = `\nšŸ“Œ More Information: ${links.join(" ā€¢ ")}`; + } + } + + let response = `Price History for ${tokenInfo} on ${chainName}${tokenLinks}\n`; + response += `Period: ${startDate} to ${endDate} (${TIME_INTERVALS[interval]} intervals)\n\n`; + + if (!data.success || !data.data.items || data.data.items.length === 0) { + return response + "No price data found for this period."; + } + + // Calculate summary statistics + const prices = data.data.items.map((d) => d.value); + const startPrice = data.data.items[0].value; + const endPrice = data.data.items[data.data.items.length - 1].value; + const priceChange = ((endPrice - startPrice) / startPrice) * 100; + const highestPrice = Math.max(...prices); + const lowestPrice = Math.min(...prices); + const averagePrice = prices.reduce((a, b) => a + b, 0) / prices.length; + const volatility = ((highestPrice - lowestPrice) / averagePrice) * 100; + + response += `šŸ“Š Summary\n`; + response += `ā€¢ Start Price: ${formatValue(startPrice)}\n`; + response += `ā€¢ End Price: ${formatValue(endPrice)}\n`; + response += `ā€¢ Price Change: ${priceChange >= 0 ? "+" : ""}${priceChange.toFixed(2)}%\n`; + response += `ā€¢ Highest Price: ${formatValue(highestPrice)}\n`; + response += `ā€¢ Lowest Price: ${formatValue(lowestPrice)}\n`; + response += `ā€¢ Average Price: ${formatValue(averagePrice)}\n`; + response += `ā€¢ Volatility: ${volatility.toFixed(2)}%\n\n`; + + // Add trend analysis + const trendStrength = Math.abs(priceChange); + let trendAnalysis = ""; + if (trendStrength < 1) { + trendAnalysis = "Price has remained relatively stable"; + } else if (trendStrength < 5) { + trendAnalysis = + priceChange > 0 + ? "Price shows slight upward movement" + : "Price shows slight downward movement"; + } else if (trendStrength < 10) { + trendAnalysis = + priceChange > 0 + ? "Price demonstrates moderate upward trend" + : "Price demonstrates moderate downward trend"; + } else { + trendAnalysis = + priceChange > 0 + ? "Price exhibits strong upward momentum" + : "Price exhibits strong downward momentum"; + } + + response += `šŸ“ˆ Trend Analysis\n`; + response += `ā€¢ ${trendAnalysis}\n`; + response += `ā€¢ Volatility is ${volatility < 10 ? "low" : volatility < 25 ? "moderate" : "high"}\n\n`; + + // Show key price points + response += `šŸ”‘ Key Price Points\n`; + const keyPoints = [ + { + label: "Start", + price: data.data.items[0].value, + timestamp: data.data.items[0].unixTime, + }, + { + label: "High", + price: highestPrice, + timestamp: data.data.items[prices.indexOf(highestPrice)].unixTime, + }, + { + label: "Low", + price: lowestPrice, + timestamp: data.data.items[prices.indexOf(lowestPrice)].unixTime, + }, + { + label: "End", + price: data.data.items[data.data.items.length - 1].value, + timestamp: data.data.items[data.data.items.length - 1].unixTime, + }, + ]; + + keyPoints.forEach((point) => { + response += `ā€¢ ${point.label}: ${formatValue(point.price)} (${formatTimestamp(point.timestamp)})\n`; + }); + + return response; +}; + +export const getPriceHistoryAction: Action = { + name: "GET_PRICE_HISTORY", + similes: [ + "SHOW_PRICE_HISTORY", + "VIEW_PRICE_HISTORY", + "CHECK_PRICE_HISTORY", + "DISPLAY_PRICE_HISTORY", + "ANALYZE_PRICE_HISTORY", + "GET_HISTORICAL_PRICES", + "SHOW_HISTORICAL_PRICES", + "VIEW_PRICE_TREND", + "CHECK_PRICE_TREND", + "ANALYZE_PRICE_TREND", + "PRICE_PERFORMANCE", + "TOKEN_PERFORMANCE", + ], + description: + "Retrieve and analyze historical price data for a token, including price changes, trends, and key statistics over a specified time period.", + validate: async ( + _runtime: IAgentRuntime, + message: Memory, + _state: State | undefined + ): Promise => { + return containsPriceHistoryKeyword(message.content.text); + }, + handler: (async ( + runtime: IAgentRuntime, + message: Memory, + _state: State | undefined, + _options: any, + callback: HandlerCallback + ): Promise => { + const callbackData: Content = { + text: "", + action: "GET_PRICE_HISTORY_RESPONSE", + source: message.content.source, + }; + + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + callbackData.text = + "I'm unable to fetch the price history due to missing API credentials."; + await callback(callbackData); + return callbackData; + } + + const messageText = message.content.text; + const addresses = extractContractAddresses(messageText); + if (addresses.length === 0) { + callbackData.text = + "I couldn't find a valid token address in your message."; + await callback(callbackData); + return callbackData; + } + + const chain = extractChain(messageText); + const timeRange = extractTimeRange(messageText); + const interval = extractTimeInterval(messageText); + + // First fetch token metadata + const tokenMetadata = await getTokenMetadata( + apiKey, + addresses[0], + chain + ); + + elizaLogger.info( + `PRICE HISTORY action activated for token ${addresses[0]} from ${new Date( + timeRange.start * 1000 + ).toLocaleString()} to ${new Date( + timeRange.end * 1000 + ).toLocaleString()} on ${chain} with ${TIME_INTERVALS[interval]} interval` + ); + + const priceData = await getPriceHistory( + apiKey, + addresses[0], + timeRange.start, + timeRange.end, + chain, + interval + ); + + if (!priceData) { + callbackData.text = + "I apologize, but I couldn't retrieve the price history data at the moment."; + await callback(callbackData); + return callbackData; + } + + callbackData.text = formatPriceHistoryResponse( + priceData, + tokenMetadata, + timeRange, + chain, + interval + ); + await callback(callbackData); + return callbackData; + }) as Handler, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Show me the daily price history for token 0x1234... on Ethereum for the last week", + }, + }, + { + user: "{{user2}}", + content: { + text: "Here's the detailed daily price history analysis for the token, including price changes, trends, and key statistics over the specified period.", + action: "GET_PRICE_HISTORY", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "What's the hourly price trend for ABC123... on Solana?", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll analyze the hourly price history and provide you with a comprehensive overview of the token's performance.", + action: "GET_PRICE_HISTORY", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Get 5-minute interval price data for token XYZ... on BSC", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll fetch the price history with 5-minute intervals and analyze the detailed price movements.", + action: "GET_PRICE_HISTORY", + }, + }, + ], + ] as ActionExample[][], +}; diff --git a/packages/plugin-birdeye/src/actions/defi/get-supported-networks.ts b/packages/plugin-birdeye/src/actions/defi/get-supported-networks.ts new file mode 100644 index 0000000000..0f476e9fd8 --- /dev/null +++ b/packages/plugin-birdeye/src/actions/defi/get-supported-networks.ts @@ -0,0 +1,147 @@ +import { + Action, + ActionExample, + Content, + Handler, + HandlerCallback, + IAgentRuntime, + Memory, + State, + elizaLogger, +} from "@elizaos/core"; +import { BASE_URL, makeApiRequest } from "../../providers/utils"; + +// Constants for keyword matching +const NETWORK_KEYWORDS = [ + "supported networks", + "available networks", + "supported chains", + "available chains", + "which networks", + "which chains", + "list networks", + "list chains", + "show networks", + "show chains", + "network support", + "chain support", +] as const; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const exampleResponse = { + success: true, + data: [ + "solana", + "ethereum", + "arbitrum", + "avalanche", + "bsc", + "optimism", + "polygon", + "base", + "zksync", + "sui", + ], +}; + +// Helper function to check if text contains network-related keywords +const containsNetworkKeyword = (text: string): boolean => { + return NETWORK_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +export const getSupportedNetworksAction: Action = { + name: "GET_SUPPORTED_NETWORKS", + similes: [ + "LIST_NETWORKS", + "SHOW_NETWORKS", + "AVAILABLE_NETWORKS", + "SUPPORTED_CHAINS", + "LIST_CHAINS", + "SHOW_CHAINS", + "NETWORK_SUPPORT", + "CHAIN_SUPPORT", + ], + description: + "Retrieve and display the list of networks supported by the Birdeye API for token information, swaps, prices, and market data.", + validate: async ( + _runtime: IAgentRuntime, + message: Memory, + _state: State | undefined + ): Promise => { + return containsNetworkKeyword(message.content.text); + }, + handler: (async ( + runtime: IAgentRuntime, + message: Memory, + _state: State | undefined, + _options: any, + callback: HandlerCallback + ): Promise => { + const callbackData: Content = { + text: "", + action: "GET_SUPPORTED_NETWORKS_RESPONSE", + source: message.content.source, + }; + + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + callbackData.text = + "I'm unable to fetch the supported networks due to missing API credentials."; + await callback(callbackData); + return callbackData; + } + + elizaLogger.info("Fetching supported networks"); + const url = `${BASE_URL}/defi/networks`; + + const networksData = await makeApiRequest(url, { + apiKey, + }); + + if (!networksData) { + callbackData.text = + "I apologize, but I couldn't retrieve the list of supported networks at the moment."; + await callback(callbackData); + return callbackData; + } + + callbackData.text = `Currently supported networks for information about tokens, swaps, prices, gainers and losers are: ${networksData.data.join(", ")}`; + await callback(callbackData); + return callbackData; + }) as Handler, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "What networks are supported?", + }, + }, + { + user: "{{user2}}", + content: { + text: "Here are the currently supported networks: solana, ethereum, arbitrum, avalanche, bsc, optimism, polygon, base, zksync, sui", + action: "GET_SUPPORTED_NETWORKS", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Show me the available chains", + }, + }, + { + user: "{{user2}}", + content: { + text: "The available chains are: solana, ethereum, arbitrum, avalanche, bsc, optimism, polygon, base, zksync, sui", + action: "GET_SUPPORTED_NETWORKS", + }, + }, + ], + ] as ActionExample[][], +}; diff --git a/packages/plugin-birdeye/src/actions/defi/get-token-metadata.ts b/packages/plugin-birdeye/src/actions/defi/get-token-metadata.ts new file mode 100644 index 0000000000..705c22ee12 --- /dev/null +++ b/packages/plugin-birdeye/src/actions/defi/get-token-metadata.ts @@ -0,0 +1,393 @@ +import { + Action, + ActionExample, + Content, + elizaLogger, + Handler, + HandlerCallback, + IAgentRuntime, + Memory, + State, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + CHAIN_KEYWORDS, + extractChain, + extractContractAddresses, + makeApiRequest, +} from "../../providers/utils"; + +// Define explicit interface instead of using typeof +export interface TokenMetadataResponse { + data: { + address: string; + symbol: string; + name: string; + decimals: number; + extensions: { + coingecko_id?: string; + website?: string; + twitter?: string; + discord?: string; + medium?: string; + }; + logo_uri?: string; + }; + success: boolean; +} + +// Constants for keyword matching +const METADATA_KEYWORDS = [ + "metadata", + "token info", + "token information", + "token details", + "token data", + "token description", + "token profile", + "token overview", + "token stats", + "token statistics", + "token social", + "token links", + "token website", + "token socials", +] as const; + +// Helper function to check if text contains metadata-related keywords +const containsMetadataKeyword = (text: string): boolean => { + return METADATA_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +export const getTokenMetadata = async ( + apiKey: string, + contractAddress: string, + chain: Chain +): Promise => { + try { + // Validate address format based on chain + const isValidAddress = (() => { + switch (chain) { + case "solana": + return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test( + contractAddress + ); + case "sui": + return /^0x[a-fA-F0-9]{64}$/i.test(contractAddress); + case "ethereum": + case "arbitrum": + case "avalanche": + case "bsc": + case "optimism": + case "polygon": + case "base": + case "zksync": + return /^0x[a-fA-F0-9]{40}$/i.test(contractAddress); + default: + return false; + } + })(); + + if (!isValidAddress) { + elizaLogger.error( + `Invalid address format for ${chain}: ${contractAddress}` + ); + return null; + } + + const params = new URLSearchParams({ + address: contractAddress, + }); + const url = `${BASE_URL}/defi/v3/token/meta-data/single?${params.toString()}`; + + elizaLogger.info( + `Fetching token metadata for ${contractAddress} on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { + apiKey, + chain, + }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching token metadata:", error.message); + } + return null; + } +}; + +const formatSocialLinks = (data: TokenMetadataResponse["data"]): string => { + const links = []; + const { extensions } = data; + + if (!extensions) { + return "No social links available"; + } + + if (extensions.website) { + links.push(`šŸŒ [Website](${extensions.website})`); + } + if (extensions.twitter) { + links.push(`šŸ¦ [Twitter](${extensions.twitter})`); + } + if (extensions.discord) { + links.push(`šŸ’¬ [Discord](${extensions.discord})`); + } + if (extensions.medium) { + links.push(`šŸ“ [Medium](${extensions.medium})`); + } + if (extensions.coingecko_id) { + links.push( + `šŸ¦Ž [CoinGecko](https://www.coingecko.com/en/coins/${extensions.coingecko_id})` + ); + } + + return links.length > 0 ? links.join("\n") : "No social links available"; +}; + +const formatMetadataResponse = ( + data: TokenMetadataResponse, + chain: Chain +): string => { + const tokenData = data.data; + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + const chainExplorer = (() => { + switch (chain) { + case "solana": + return `https://solscan.io/token/${tokenData.address}`; + case "ethereum": + return `https://etherscan.io/token/${tokenData.address}`; + case "arbitrum": + return `https://arbiscan.io/token/${tokenData.address}`; + case "avalanche": + return `https://snowtrace.io/token/${tokenData.address}`; + case "bsc": + return `https://bscscan.com/token/${tokenData.address}`; + case "optimism": + return `https://optimistic.etherscan.io/token/${tokenData.address}`; + case "polygon": + return `https://polygonscan.com/token/${tokenData.address}`; + case "base": + return `https://basescan.org/token/${tokenData.address}`; + case "zksync": + return `https://explorer.zksync.io/address/${tokenData.address}`; + case "sui": + return `https://suiscan.xyz/mainnet/object/${tokenData.address}`; + default: + return null; + } + })(); + + let response = `Token Metadata for ${tokenData.name} (${tokenData.symbol}) on ${chainName}\n\n`; + + // Basic Information + response += "šŸ“ Basic Information\n"; + response += `ā€¢ Name: ${tokenData.name}\n`; + response += `ā€¢ Symbol: ${tokenData.symbol}\n`; + response += `ā€¢ Address: ${tokenData.address}\n`; + response += `ā€¢ Decimals: ${tokenData.decimals}\n`; + if (chainExplorer) { + response += `ā€¢ Explorer: [View on ${chainName} Explorer](${chainExplorer})\n`; + } + + // Social Links + response += "\nšŸ”— Social Links & Extensions\n"; + response += formatSocialLinks(tokenData) + "\n"; + + // Logo + if (tokenData.logo_uri) { + response += "\nšŸ–¼ļø Logo\n"; + response += tokenData.logo_uri; + } + + return response; +}; + +export const getTokenMetadataAction: Action = { + name: "GET_TOKEN_METADATA", + similes: [ + "SHOW_TOKEN_INFO", + "VIEW_TOKEN_DETAILS", + "CHECK_TOKEN_METADATA", + "DISPLAY_TOKEN_INFO", + "GET_TOKEN_DETAILS", + "TOKEN_INFORMATION", + "TOKEN_PROFILE", + "TOKEN_OVERVIEW", + "TOKEN_SOCIAL_LINKS", + "TOKEN_STATISTICS", + "TOKEN_DESCRIPTION", + ], + description: + "Retrieve and display comprehensive token metadata including basic information, description, social links, and other relevant details.", + validate: async ( + _runtime: IAgentRuntime, + message: Memory, + _state: State | undefined + ): Promise => { + return containsMetadataKeyword(message.content.text); + }, + handler: (async ( + runtime: IAgentRuntime, + message: Memory, + _state: State | undefined, + _options: any, + callback: HandlerCallback + ): Promise => { + const callbackData: Content = { + text: "", + action: "GET_TOKEN_METADATA_RESPONSE", + source: message.content.source, + }; + + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + callbackData.text = + "I'm unable to fetch the token metadata due to missing API credentials."; + await callback(callbackData); + return callbackData; + } + + const messageText = message.content.text; + const addresses = extractContractAddresses(messageText); + const chain = extractChain(messageText); + + // Check if a specific chain was mentioned + const isChainMentioned = CHAIN_KEYWORDS.some((keyword) => + messageText.toLowerCase().includes(keyword.toLowerCase()) + ); + + if (addresses.length === 0) { + callbackData.text = isChainMentioned + ? `I couldn't find a valid token address for ${chain} chain in your message. ${chain} addresses should match the format: ${getChainAddressFormat(chain)}` + : "I couldn't find a valid token address in your message."; + await callback(callbackData); + return callbackData; + } + + // Validate that the address matches the specified chain format + const isValidForChain = (() => { + switch (chain) { + case "solana": + return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(addresses[0]); + case "sui": + return ( + /^0x[a-fA-F0-9]{64}$/i.test(addresses[0]) || + /^0x[a-fA-F0-9]{64}::[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$/i.test( + addresses[0] + ) + ); + case "ethereum": + case "arbitrum": + case "avalanche": + case "bsc": + case "optimism": + case "polygon": + case "base": + case "zksync": + return /^0x[a-fA-F0-9]{40}$/i.test(addresses[0]); + default: + return false; + } + })(); + + if (!isValidForChain && isChainMentioned) { + callbackData.text = `The provided address doesn't match the format for ${chain} chain. ${chain} addresses should match the format: ${getChainAddressFormat(chain)}`; + await callback(callbackData); + return callbackData; + } + + elizaLogger.info( + `TOKEN METADATA action activated for ${addresses[0]} on ${chain}` + ); + + const metadataData = await getTokenMetadata( + apiKey, + addresses[0], + chain + ); + + if (!metadataData) { + callbackData.text = + "I apologize, but I couldn't retrieve the token metadata at the moment."; + await callback(callbackData); + return callbackData; + } + + callbackData.text = formatMetadataResponse(metadataData, chain); + await callback(callbackData); + return callbackData; + }) as Handler, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Show me the token information for 0x1234... on Ethereum", + }, + }, + { + user: "{{user2}}", + content: { + text: "Here's the detailed token metadata including basic information, social links, and other relevant details.", + action: "GET_TOKEN_METADATA", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "What are the token details for ABC123... on Solana?", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll fetch and display the comprehensive token profile with all available information.", + action: "GET_TOKEN_METADATA", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Get me the social links and description for token XYZ... on BSC", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll retrieve the token's metadata including its social media links and description.", + action: "GET_TOKEN_METADATA", + }, + }, + ], + ] as ActionExample[][], +}; + +const getChainAddressFormat = (chain: Chain): string => { + switch (chain) { + case "solana": + return "Base58 string (32-44 characters)"; + case "sui": + return "0x followed by 64 hexadecimal characters, optionally followed by ::module::type"; + case "ethereum": + case "arbitrum": + case "avalanche": + case "bsc": + case "optimism": + case "polygon": + case "base": + case "zksync": + return "0x followed by 40 hexadecimal characters"; + default: + return "unknown format"; + } +}; diff --git a/packages/plugin-birdeye/src/actions/defi/get-token-trades.ts b/packages/plugin-birdeye/src/actions/defi/get-token-trades.ts new file mode 100644 index 0000000000..d7883cac20 --- /dev/null +++ b/packages/plugin-birdeye/src/actions/defi/get-token-trades.ts @@ -0,0 +1,446 @@ +import { + Action, + ActionExample, + Content, + elizaLogger, + Handler, + HandlerCallback, + IAgentRuntime, + Memory, + State, +} from "@elizaos/core"; +import { + BASE_URL, + Chain, + extractChain, + extractContractAddresses, + extractLimit, + formatTimestamp, + formatValue, + makeApiRequest, + shortenAddress, +} from "../../providers/utils"; +import { getTokenMetadata, TokenMetadataResponse } from "./get-token-metadata"; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const exampleResponse = { + success: true, + data: { + items: [ + { + quote: { + symbol: "POTUS", + decimals: 6, + address: "7hyHfdgxwtaj1QpQSJw3s4R2LMxShPpmr2GsCw9npump", + amount: 13922809, + feeInfo: null, + uiAmount: 13.922809, + price: null, + nearestPrice: 0.00008581853084042329, + changeAmount: 13922809, + uiChangeAmount: 13.922809, + }, + base: { + symbol: "SOL", + decimals: 9, + address: "So11111111111111111111111111111111111111112", + amount: 9090, + uiAmount: 0.00000909, + price: null, + nearestPrice: 128.201119598627, + changeAmount: -9090, + uiChangeAmount: -0.00000909, + }, + basePrice: null, + quotePrice: null, + txHash: "5G72183B77KafzKv4GEJNnDrV7rtspv5X5WM9yG9g1P89iG1UCGBuAqcMasgGhRYN24bmWsNPkQqptRbX5uoH44K", + source: "raydium", + blockUnixTime: 1726676178, + txType: "swap", + owner: "AavgaV4YKned3RN6JVMANKmAaVS2Tpfnw88HbYtzgBAn", + side: "sell", + alias: null, + pricePair: 1531662.1562156219, + from: { + symbol: "SOL", + decimals: 9, + address: "So11111111111111111111111111111111111111112", + amount: 9090, + uiAmount: 0.00000909, + price: null, + nearestPrice: 128.201119598627, + changeAmount: -9090, + uiChangeAmount: -0.00000909, + }, + to: { + symbol: "POTUS", + decimals: 6, + address: "7hyHfdgxwtaj1QpQSJw3s4R2LMxShPpmr2GsCw9npump", + amount: 13922809, + feeInfo: null, + uiAmount: 13.922809, + price: null, + nearestPrice: 0.00008581853084042329, + changeAmount: 13922809, + uiChangeAmount: 13.922809, + }, + tokenPrice: null, + poolId: "2L8fo6g6me9ZubZhH2iiz6616GouRbGeEuvNoGv69xWE", + }, + { + quote: { + symbol: "PEAKY", + decimals: 6, + address: "62uBW5K24PdxXk185tNjz9pwzkpHinKt8qZznxPPpump", + amount: 35238136, + feeInfo: null, + uiAmount: 35.238136, + price: null, + nearestPrice: 0.00003742796781669965, + changeAmount: 35238136, + uiChangeAmount: 35.238136, + }, + base: { + symbol: "SOL", + decimals: 9, + address: "So11111111111111111111111111111111111111112", + amount: 10333, + uiAmount: 0.000010333, + price: null, + nearestPrice: 128.201119598627, + changeAmount: -10333, + uiChangeAmount: -0.000010333, + }, + basePrice: null, + quotePrice: null, + txHash: "zXdSLDTX4MVzunVJgFbJmiLY9z2hZ3n28w6bYvKn1aZVL1QZGozkyMMMteFqpyWraUTdRyX1GKFnJYkqPsL5SJK", + source: "raydium", + blockUnixTime: 1726676178, + txType: "swap", + owner: "CDt3xtwPVWDbhENL3QhDX5XYVx9JNCvewfdfCRy1cKFt", + side: "sell", + alias: null, + pricePair: 3410252.2016839255, + from: { + symbol: "SOL", + decimals: 9, + address: "So11111111111111111111111111111111111111112", + amount: 10333, + uiAmount: 0.000010333, + price: null, + nearestPrice: 128.201119598627, + changeAmount: -10333, + uiChangeAmount: -0.000010333, + }, + to: { + symbol: "PEAKY", + decimals: 6, + address: "62uBW5K24PdxXk185tNjz9pwzkpHinKt8qZznxPPpump", + amount: 35238136, + feeInfo: null, + uiAmount: 35.238136, + price: null, + nearestPrice: 0.00003742796781669965, + changeAmount: 35238136, + uiChangeAmount: 35.238136, + }, + tokenPrice: null, + poolId: "5vsk6iYjKXEo6x7maZJwh36UjqwFxkRtoHK5Nphh3ht1", + }, + ], + hasNext: true, + }, +}; + +type TokenTradesResponse = typeof exampleResponse; + +// Constants for keyword matching +const TOKEN_TRADES_KEYWORDS = [ + "token trades", + "token swaps", + "token transactions", + "token activity", + "token orders", + "token executions", + "token trading", + "token market activity", + "token exchange activity", + "token trading history", + "token market history", + "token exchange history", +] as const; + +// Helper function to check if text contains trades-related keywords +const containsTokenTradesKeyword = (text: string): boolean => { + return TOKEN_TRADES_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; + +const getTokenTrades = async ( + apiKey: string, + contractAddress: string, + chain: Chain, + limit: number +): Promise => { + try { + const params = new URLSearchParams({ + address: contractAddress, + limit: limit.toString(), + }); + const url = `${BASE_URL}/defi/trades_token?${params.toString()}`; + + elizaLogger.info( + `Fetching token trades for ${contractAddress} on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { + apiKey, + chain, + }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching token trades:", error.message); + } + return null; + } +}; + +const formatTrade = ( + trade: TokenTradesResponse["data"]["items"][0] +): string => { + const timestamp = formatTimestamp(trade.blockUnixTime); + const side = trade.side === "buy" ? "šŸŸ¢ Buy" : "šŸ”“ Sell"; + const baseAmount = formatValue(trade.base.uiAmount); + const quoteAmount = formatValue(trade.quote.uiAmount); + + let response = `${side} - ${timestamp}\n`; + response += `ā€¢ ${baseAmount} ${trade.base.symbol} ā‡„ ${quoteAmount} ${trade.quote.symbol}\n`; + response += `ā€¢ Source: ${trade.source}\n`; + response += `ā€¢ Owner: ${shortenAddress(trade.owner)}\n`; + response += `ā€¢ Tx: ${shortenAddress(trade.txHash)}`; + + return response; +}; + +const formatTokenTradesResponse = ( + data: TokenTradesResponse, + tokenMetadata: TokenMetadataResponse | null, + chain: Chain +): string => { + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + + let tokenInfo = "Unknown Token"; + let tokenLinks = ""; + + if (tokenMetadata?.success) { + const { name, symbol, extensions } = tokenMetadata.data; + tokenInfo = `${name} (${symbol})`; + + const links = []; + if (extensions.website) links.push(`[Website](${extensions.website})`); + if (extensions.coingecko_id) + links.push( + `[CoinGecko](https://www.coingecko.com/en/coins/${extensions.coingecko_id})` + ); + if (links.length > 0) { + tokenLinks = `\nšŸ“Œ More Information: ${links.join(" ā€¢ ")}`; + } + } + + let response = `Recent Trades for ${tokenInfo} on ${chainName}${tokenLinks}\n\n`; + + if (!data.success || !data.data.items || data.data.items.length === 0) { + return response + "No trades found."; + } + + const trades = data.data.items; + + // Calculate summary statistics + const buyTrades = trades.filter((t) => t.side === "buy"); + const buyRatio = (buyTrades.length / trades.length) * 100; + + const baseVolume = trades.reduce( + (sum, t) => sum + Math.abs(t.base.uiAmount), + 0 + ); + const quoteVolume = trades.reduce( + (sum, t) => sum + Math.abs(t.quote.uiAmount), + 0 + ); + const averageBaseAmount = baseVolume / trades.length; + const averageQuoteAmount = quoteVolume / trades.length; + + response += `šŸ“Š Summary\n`; + response += `ā€¢ Total Trades: ${trades.length}\n`; + response += `ā€¢ Buy/Sell Ratio: ${buyRatio.toFixed(1)}% buys\n`; + response += `ā€¢ Total Volume: ${formatValue(baseVolume)} ${trades[0].base.symbol}\n`; + response += `ā€¢ Average Trade Size: ${formatValue(averageBaseAmount)} ${trades[0].base.symbol}\n`; + response += `ā€¢ Total Quote Volume: ${formatValue(quoteVolume)} ${trades[0].quote.symbol}\n`; + response += `ā€¢ Average Quote Size: ${formatValue(averageQuoteAmount)} ${trades[0].quote.symbol}\n\n`; + + // Add market analysis + const tradeFrequency = + trades.length > 20 ? "high" : trades.length > 10 ? "moderate" : "low"; + const volumeLevel = + baseVolume > averageBaseAmount * 2 + ? "high" + : baseVolume > averageBaseAmount + ? "moderate" + : "low"; + const marketAnalysis = `Market shows ${tradeFrequency} trading activity with ${volumeLevel} volume per trade.`; + + response += `šŸ“ˆ Market Analysis\n`; + response += `ā€¢ ${marketAnalysis}\n\n`; + + response += `šŸ”„ Recent Trades\n`; + trades.forEach((trade, index) => { + response += `${index + 1}. ${formatTrade(trade)}\n\n`; + }); + + if (data.data.hasNext) { + response += `Note: More trades are available. This is a limited view of the most recent activity.`; + } + + return response; +}; + +export const getTokenTradesAction: Action = { + name: "GET_TOKEN_TRADES", + similes: [ + "SHOW_TOKEN_TRADES", + "VIEW_TOKEN_TRADES", + "CHECK_TOKEN_TRADES", + "DISPLAY_TOKEN_TRADES", + "GET_TRADE_HISTORY", + "SHOW_TRADE_HISTORY", + "VIEW_TRADING_ACTIVITY", + "CHECK_MARKET_ACTIVITY", + "TOKEN_TRADING_HISTORY", + "TOKEN_MARKET_ACTIVITY", + ], + description: + "Retrieve and analyze recent trading activity for a token, including trade details, volume statistics, and market analysis.", + validate: async ( + _runtime: IAgentRuntime, + message: Memory, + _state: State | undefined + ): Promise => { + return containsTokenTradesKeyword(message.content.text); + }, + handler: (async ( + runtime: IAgentRuntime, + message: Memory, + _state: State | undefined, + _options: any, + callback: HandlerCallback + ): Promise => { + const callbackData: Content = { + text: "", + action: "GET_TOKEN_TRADES_RESPONSE", + source: message.content.source, + }; + + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); + callbackData.text = + "I'm unable to fetch the token trades due to missing API credentials."; + await callback(callbackData); + return callbackData; + } + + const messageText = message.content.text; + const addresses = extractContractAddresses(messageText); + if (addresses.length === 0) { + callbackData.text = + "I couldn't find a valid token address in your message."; + await callback(callbackData); + return callbackData; + } + + const chain = extractChain(messageText); + const limit = extractLimit(messageText); + + // First fetch token metadata + const tokenMetadata = await getTokenMetadata( + apiKey, + addresses[0], + chain + ); + + elizaLogger.info( + `TOKEN TRADES action activated for ${addresses[0]} on ${chain}` + ); + + const tradesData = await getTokenTrades( + apiKey, + addresses[0], + chain, + limit + ); + + if (!tradesData) { + callbackData.text = + "I apologize, but I couldn't retrieve the token trades at the moment."; + await callback(callbackData); + return callbackData; + } + + callbackData.text = formatTokenTradesResponse( + tradesData, + tokenMetadata, + chain + ); + await callback(callbackData); + return callbackData; + }) as Handler, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Show me recent trades for token 0x1234... on Ethereum", + }, + }, + { + user: "{{user2}}", + content: { + text: "Here's the detailed trading activity analysis including recent trades, volume statistics, and market insights.", + action: "GET_TOKEN_TRADES", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "What's the trading activity for ABC123... on Solana?", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll analyze the recent trading activity and provide you with a comprehensive overview of the market.", + action: "GET_TOKEN_TRADES", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Get me the last 20 trades for token XYZ... on BSC", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll fetch the recent trades and provide detailed statistics about the trading activity.", + action: "GET_TOKEN_TRADES", + }, + }, + ], + ] as ActionExample[][], +}; diff --git a/packages/plugin-birdeye/src/index.ts b/packages/plugin-birdeye/src/index.ts index c73b2d36dd..f686940ea5 100644 --- a/packages/plugin-birdeye/src/index.ts +++ b/packages/plugin-birdeye/src/index.ts @@ -1,56 +1,45 @@ import { Plugin } from "@elizaos/core"; -import { - gainersLosersProvider, - ohlcvProvider, - pairOverviewProvider, - priceMultipleProvider, - priceProvider, - priceVolumeProvider, - tokenCreationProvider, - tokenListProvider, - tokenMarketDataProvider, - tokenOverviewProvider, - tokenSecurityProvider, - tokenTradeProvider, - tradesSeekProvider, - transactionHistoryProvider, - trendingTokensProvider, - walletPortfolioProvider, -} from "./providers"; +import { getOHLCVAction } from "./actions/defi/get-ohlcv"; +import { getPriceHistoryAction } from "./actions/defi/get-price-history"; +import { getSupportedNetworksAction } from "./actions/defi/get-supported-networks"; +import { getTokenMetadataAction } from "./actions/defi/get-token-metadata"; +import { getTokenTradesAction } from "./actions/defi/get-token-trades"; export const birdeyePlugin: Plugin = { name: "birdeye", description: "Birdeye Plugin for token data and analytics", - actions: [], + actions: [ + getSupportedNetworksAction, + getTokenMetadataAction, + getPriceHistoryAction, + getOHLCVAction, + getTokenTradesAction, + ], evaluators: [], providers: [ - // DeFi providers - priceProvider, - priceMultipleProvider, - ohlcvProvider, - priceVolumeProvider, - - // Pair providers - pairOverviewProvider, - - // Search providers - tokenMarketDataProvider, - - // Token providers - tokenOverviewProvider, - tokenSecurityProvider, - tokenListProvider, - trendingTokensProvider, - tokenCreationProvider, - tokenTradeProvider, - - // Trader providers - gainersLosersProvider, - tradesSeekProvider, - - // Wallet providers - transactionHistoryProvider, - walletPortfolioProvider, + // networksProvider, + // // DeFi providers + // priceProvider, + // priceMultipleProvider, + // ohlcvProvider, + // priceVolumeProvider, + // // Pair providers + // pairOverviewProvider, + // // Search providers + // tokenMarketDataProvider, + // // Token providers + // tokenOverviewProvider, + // tokenSecurityProvider, + // tokenListProvider, + // trendingTokensProvider, + // tokenCreationProvider, + // tokenTradeProvider, + // // Trader providers + // gainersLosersProvider, + // tradesSeekProvider, + // // Wallet providers + // transactionHistoryProvider, + // walletPortfolioProvider, ], }; diff --git a/packages/plugin-birdeye/src/providers/defi/networks-provider.ts b/packages/plugin-birdeye/src/providers/defi/networks-provider.ts index 458cbb881b..a13659bc3c 100644 --- a/packages/plugin-birdeye/src/providers/defi/networks-provider.ts +++ b/packages/plugin-birdeye/src/providers/defi/networks-provider.ts @@ -7,20 +7,6 @@ import { } from "@elizaos/core"; import { BASE_URL, makeApiRequest } from "../utils"; -// Types -interface NetworkInfo { - name: string; - chainId: string; - rpcUrl: string; - explorerUrl: string; - status: "active" | "maintenance" | "deprecated"; - features: string[]; -} - -interface NetworksResponse { - networks: NetworkInfo[]; -} - // Constants const NETWORK_KEYWORDS = [ "supported networks", @@ -44,69 +30,22 @@ const containsNetworkKeyword = (text: string): boolean => { ); }; -const getNetworks = async ( - apiKey: string -): Promise => { - try { - const url = `${BASE_URL}/defi/networks`; - - elizaLogger.info("Fetching supported networks from:", url); - - return await makeApiRequest(url, { - apiKey, - chain: "solana", - }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching networks:", error.message); - } - return null; - } -}; - -const formatNetworkResponse = (data: NetworksResponse): string => { - let response = "Supported Networks on Birdeye\n\n"; - - // Group networks by status - const activeNetworks = data.networks.filter((n) => n.status === "active"); - const maintenanceNetworks = data.networks.filter( - (n) => n.status === "maintenance" - ); - const deprecatedNetworks = data.networks.filter( - (n) => n.status === "deprecated" - ); - - // Format active networks - if (activeNetworks.length > 0) { - response += "šŸŸ¢ Active Networks\n"; - activeNetworks.forEach((network) => { - response += `ā€¢ ${network.name}\n`; - response += ` - Chain ID: ${network.chainId}\n`; - response += ` - Features: ${network.features.join(", ")}\n`; - response += ` - Explorer: ${network.explorerUrl}\n\n`; - }); - } - - // Format maintenance networks - if (maintenanceNetworks.length > 0) { - response += "šŸŸ” Networks Under Maintenance\n"; - maintenanceNetworks.forEach((network) => { - response += `ā€¢ ${network.name}\n`; - response += ` - Chain ID: ${network.chainId}\n`; - response += ` - Features: ${network.features.join(", ")}\n\n`; - }); - } - - // Format deprecated networks - if (deprecatedNetworks.length > 0) { - response += "šŸ”“ Deprecated Networks\n"; - deprecatedNetworks.forEach((network) => { - response += `ā€¢ ${network.name}\n`; - response += ` - Chain ID: ${network.chainId}\n\n`; - }); - } - - return response.trim(); +// use sample response to simplify type generation +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const sampleResponse = { + data: [ + "solana", + "ethereum", + "arbitrum", + "avalanche", + "bsc", + "optimism", + "polygon", + "base", + "zksync", + "sui", + ], + success: true, }; export const networksProvider: Provider = { @@ -129,12 +68,20 @@ export const networksProvider: Provider = { elizaLogger.info("NETWORKS provider activated"); - const networksData = await getNetworks(apiKey); + const url = `${BASE_URL}/defi/networks`; + + elizaLogger.info("Fetching supported networks from:", url); + + const networksData = await makeApiRequest(url, { + apiKey, + }); + + console.log(JSON.stringify(networksData, null, 2)); if (!networksData) { return null; } - return formatNetworkResponse(networksData); + return `Currently supported networks for information about tokens, swaps, prices, gainers and losers are: ${networksData.data.join(", ")}`; }, }; diff --git a/packages/plugin-birdeye/src/providers/index.ts b/packages/plugin-birdeye/src/providers/index.ts deleted file mode 100644 index de86d2629c..0000000000 --- a/packages/plugin-birdeye/src/providers/index.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { Provider } from "@elizaos/core"; - -// Import all providers -import { - baseQuoteOHLCVProvider, - networksProvider, - ohlcvProvider, - pairOHLCVProvider, - pairTradesProvider, - pairTradesSeekProvider, - priceHistoryProvider, - priceMultipleProvider, - priceProvider, - priceVolumeProvider, - tokenTradesProvider, - tokenTradesSeekProvider, -} from "./defi"; -import { pairOverviewProvider } from "./pair"; -import { tokenMarketDataProvider } from "./search"; -import { - allMarketListProvider, - newListingProvider, - tokenCreationProvider, - tokenHolderProvider, - tokenListProvider, - tokenMarketProvider, - tokenMetadataProvider, - tokenMintBurnProvider, - tokenOverviewProvider, - tokenSecurityProvider, - tokenTradeProvider, - topTradersProvider, - trendingTokensProvider, -} from "./token"; -import { gainersLosersProvider, tradesSeekProvider } from "./trader"; -import { - portfolioMultichainProvider, - supportedNetworksProvider, - tokenBalanceProvider, - transactionHistoryMultichainProvider, - transactionHistoryProvider, - walletPortfolioProvider, -} from "./wallet"; - -// Export individual providers -export * from "./defi"; -export * from "./pair"; -export * from "./search"; -export * from "./token"; -export * from "./trader"; -export * from "./wallet"; - -// Export providers array -export const providers: Provider[] = [ - // DeFi providers - baseQuoteOHLCVProvider, - networksProvider, - ohlcvProvider, - pairOHLCVProvider, - pairTradesProvider, - pairTradesSeekProvider, - priceHistoryProvider, - priceMultipleProvider, - priceProvider, - priceVolumeProvider, - tokenTradesProvider, - tokenTradesSeekProvider, - - // Pair providers - pairOverviewProvider, - - // Search providers - tokenMarketDataProvider, - - // Token providers - allMarketListProvider, - newListingProvider, - tokenCreationProvider, - tokenHolderProvider, - tokenListProvider, - tokenMarketProvider, - tokenMetadataProvider, - tokenMintBurnProvider, - tokenOverviewProvider, - tokenSecurityProvider, - tokenTradeProvider, - topTradersProvider, - trendingTokensProvider, - - // Trader providers - gainersLosersProvider, - tradesSeekProvider, - - // Wallet providers - portfolioMultichainProvider, - supportedNetworksProvider, - tokenBalanceProvider, - transactionHistoryMultichainProvider, - transactionHistoryProvider, - walletPortfolioProvider, -]; - -// DeFi Providers -export * from "./defi/networks-provider"; -export * from "./defi/ohlcv-base-quote-provider"; -export * from "./defi/ohlcv-pair-provider"; -export * from "./defi/ohlcv-provider"; -export * from "./defi/pair-trades-provider"; -export * from "./defi/pair-trades-seek-provider"; -export * from "./defi/price-history-provider"; -export * from "./defi/price-multiple-provider"; -export * from "./defi/price-provider"; -export * from "./defi/price-volume-provider"; -export * from "./defi/token-trades-provider"; -export * from "./defi/trades-seek-provider"; - -// Token Providers -export * from "./token/all-market-list-provider"; -export * from "./token/new-listing-provider"; -export * from "./token/token-creation-provider"; -export * from "./token/token-holder-provider"; -export * from "./token/token-list-provider"; -export * from "./token/token-market-provider"; -export * from "./token/token-metadata-provider"; -export * from "./token/token-mint-burn-provider"; -export * from "./token/token-overview-provider"; -export * from "./token/token-security-provider"; -export * from "./token/token-trade-provider"; -export * from "./token/top-traders-provider"; -export * from "./token/trending-tokens-provider"; - -// Wallet Providers -export * from "./wallet/portfolio-multichain-provider"; -export * from "./wallet/supported-networks-provider"; -export * from "./wallet/token-balance-provider"; -export * from "./wallet/transaction-history-multichain-provider"; -export * from "./wallet/transaction-history-provider"; -export * from "./wallet/wallet-portfolio-provider"; - -// Trader Providers -export * from "./trader/gainers-losers-provider"; -export * from "./trader/trades-seek-provider"; - -// Pair Providers -export * from "./pair/pair-overview-provider"; - -// Search Providers -export * from "./search/token-market-data-provider"; diff --git a/packages/plugin-birdeye/src/providers/utils.ts b/packages/plugin-birdeye/src/providers/utils.ts index 3e7a5d1252..40db78f7a7 100644 --- a/packages/plugin-birdeye/src/providers/utils.ts +++ b/packages/plugin-birdeye/src/providers/utils.ts @@ -76,12 +76,21 @@ export const extractContractAddresses = (text: string): string[] => { const addresses: string[] = []; for (const word of words) { - // Ethereum-like addresses (0x...) + // Ethereum-like addresses (0x...) - for Ethereum, Arbitrum, Avalanche, BSC, Optimism, Polygon, Base, zkSync if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { addresses.push(word); } // Solana addresses (base58, typically 32-44 chars) - if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { + else if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { + addresses.push(word); + } + // Sui addresses - both formats: + // 1. Simple object ID: 0x followed by 64 hex chars + // 2. Full token format: 0x:::: + else if ( + /^0x[a-fA-F0-9]{64}$/i.test(word) || + /^0x[a-fA-F0-9]{64}::[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$/i.test(word) + ) { addresses.push(word); } } @@ -249,12 +258,12 @@ export async function makeApiRequest( url: string, options: { apiKey: string; - chain: Chain; + chain?: Chain; method?: "GET" | "POST"; body?: any; } ): Promise { - const { apiKey, chain, method = "GET", body } = options; + const { apiKey, chain = "solana", method = "GET", body } = options; try { const response = await fetch(url, { @@ -280,13 +289,9 @@ export async function makeApiRequest( ); } - const data: ApiResponse = await response.json(); - - if (!data.success) { - throw new Error(data.error || "Unknown API error"); - } + const responseJson: T = await response.json(); - return data.data; + return responseJson; } catch (error) { if (error instanceof BirdeyeApiError) { elizaLogger.error(`API Error (${error.status}):`, error.message); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3e979dca71..6562f8c455 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -144,6 +144,9 @@ importers: '@elizaos/plugin-aptos': specifier: workspace:* version: link:../packages/plugin-aptos + '@elizaos/plugin-birdeye': + specifier: workspace:* + version: link:../packages/plugin-birdeye '@elizaos/plugin-bootstrap': specifier: workspace:* version: link:../packages/plugin-bootstrap @@ -992,6 +995,51 @@ importers: specifier: 7.1.0 version: 7.1.0 + packages/plugin-birdeye: + dependencies: + '@coral-xyz/anchor': + specifier: 0.30.1 + version: 0.30.1(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@elizaos/core': + specifier: workspace:* + version: link:../core + '@solana/spl-token': + specifier: 0.4.9 + version: 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@solana/web3.js': + specifier: 1.95.8 + version: 1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + bignumber: + specifier: 1.1.0 + version: 1.1.0 + bignumber.js: + specifier: 9.1.2 + version: 9.1.2 + bs58: + specifier: 6.0.0 + version: 6.0.0 + fomo-sdk-solana: + specifier: 1.3.2 + version: 1.3.2(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) + form-data: + specifier: 4.0.1 + version: 4.0.1 + node-cache: + specifier: 5.1.2 + version: 5.1.2 + pumpdotfun-sdk: + specifier: 1.3.2 + version: 1.3.2(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(rollup@4.28.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + tsup: + specifier: 8.3.5 + version: 8.3.5(@swc/core@1.10.1(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(typescript@5.6.3)(yaml@2.6.1) + vitest: + specifier: 2.1.4 + version: 2.1.4(@types/node@22.10.2)(jsdom@25.0.1(bufferutil@4.0.8)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) + whatwg-url: + specifier: 7.1.0 + version: 7.1.0 + packages/plugin-bootstrap: dependencies: '@elizaos/core': diff --git a/scripts/dev.sh b/scripts/dev.sh index 22b5466f9d..9ed45c99eb 100644 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -74,7 +74,7 @@ if [ ! -d "$PACKAGES_DIR" ]; then fi # List of working folders to watch (relative to $PACKAGES_DIR) -WORKING_FOLDERS=("client-direct") # Core is handled separately +WORKING_FOLDERS=("client-direct" "plugin-birdeye") # Core is handled separately # Initialize an array to hold package-specific commands COMMANDS=() From abee6e158857cdcb7cf20d46fcb7cd6f396262a2 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Sat, 28 Dec 2024 09:06:00 -0800 Subject: [PATCH 03/33] chore: cleanup token search provider --- .../src/actions/defi/get-ohlcv.ts | 23 +- .../src/actions/defi/get-price-history.ts | 24 +- .../src/actions/defi/get-token-metadata.ts | 240 +------- .../src/actions/defi/get-token-trades.ts | 446 -------------- ...{get-supported-networks.ts => networks.ts} | 49 +- .../src/actions/defi/networks.utils.ts | 27 + packages/plugin-birdeye/src/index.ts | 41 +- .../src/providers/__tests__/utils.test.ts | 145 ----- .../src/providers/address-search-provider.ts | 111 ++++ .../defi/__tests__/price-provider.test.ts | 218 ------- .../src/providers/defi/index.ts | 12 - .../src/providers/defi/networks-provider.ts | 87 --- .../defi/ohlcv-base-quote-provider.ts | 244 -------- .../src/providers/defi/ohlcv-pair-provider.ts | 210 ------- .../src/providers/defi/ohlcv-provider.ts | 256 -------- .../providers/defi/pair-trades-provider.ts | 245 -------- .../defi/pair-trades-seek-provider.ts | 266 --------- .../providers/defi/price-history-provider.ts | 230 -------- .../providers/defi/price-multiple-provider.ts | 200 ------- .../src/providers/defi/price-provider.ts | 175 ------ .../providers/defi/price-volume-provider.ts | 234 -------- .../providers/defi/token-trades-provider.ts | 236 -------- .../providers/defi/trades-seek-provider.ts | 210 ------- .../src/providers/pair/index.ts | 1 - .../providers/pair/pair-overview-provider.ts | 286 --------- .../src/providers/search/index.ts | 1 - .../search/token-market-data-provider.ts | 214 ------- .../__tests__/token-overview-provider.test.ts | 189 ------ .../token/all-market-list-provider.ts | 114 ---- .../src/providers/token/index.ts | 13 - .../providers/token/new-listing-provider.ts | 113 ---- .../token/token-creation-provider.ts | 199 ------- .../providers/token/token-holder-provider.ts | 220 ------- .../providers/token/token-list-provider.ts | 198 ------- .../providers/token/token-market-provider.ts | 217 ------- .../token/token-metadata-provider.ts | 197 ------- .../token/token-mint-burn-provider.ts | 203 ------- .../token/token-overview-provider.ts | 266 --------- .../token/token-security-provider.ts | 238 -------- .../providers/token/token-trade-provider.ts | 327 ----------- .../providers/token/top-traders-provider.ts | 104 ---- .../token/trending-tokens-provider.ts | 270 --------- .../trader/gainers-losers-provider.ts | 228 -------- .../src/providers/trader/index.ts | 2 - .../providers/trader/trades-seek-provider.ts | 247 -------- .../plugin-birdeye/src/providers/utils.ts | 303 ---------- .../src/providers/wallet/index.ts | 6 - .../wallet/portfolio-multichain-provider.ts | 159 ----- .../wallet/supported-networks-provider.ts | 131 ----- .../wallet/token-balance-provider.ts | 135 ----- ...transaction-history-multichain-provider.ts | 174 ------ .../wallet/transaction-history-provider.ts | 381 ------------ .../wallet/wallet-portfolio-provider.ts | 335 ----------- packages/plugin-birdeye/src/services.ts | 170 ++++++ .../plugin-birdeye/src/types/search-token.ts | 43 ++ packages/plugin-birdeye/src/types/shared.ts | 23 + .../src/types/token-metadata.ts | 18 + packages/plugin-birdeye/src/types/wallet.ts | 24 + packages/plugin-birdeye/src/utils.ts | 546 ++++++++++++++++++ .../plugin-solana/src/evaluators/trust.ts | 25 +- packages/plugin-solana/src/index.ts | 17 +- .../src/providers/trustScoreProvider.ts | 39 +- src/providers/address-search.provider.ts | 9 + 63 files changed, 1070 insertions(+), 9244 deletions(-) delete mode 100644 packages/plugin-birdeye/src/actions/defi/get-token-trades.ts rename packages/plugin-birdeye/src/actions/defi/{get-supported-networks.ts => networks.ts} (72%) create mode 100644 packages/plugin-birdeye/src/actions/defi/networks.utils.ts delete mode 100644 packages/plugin-birdeye/src/providers/__tests__/utils.test.ts create mode 100644 packages/plugin-birdeye/src/providers/address-search-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/defi/__tests__/price-provider.test.ts delete mode 100644 packages/plugin-birdeye/src/providers/defi/index.ts delete mode 100644 packages/plugin-birdeye/src/providers/defi/networks-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/defi/ohlcv-base-quote-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/defi/ohlcv-pair-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/defi/ohlcv-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/defi/pair-trades-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/defi/pair-trades-seek-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/defi/price-history-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/defi/price-multiple-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/defi/price-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/defi/price-volume-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/defi/token-trades-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/defi/trades-seek-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/pair/index.ts delete mode 100644 packages/plugin-birdeye/src/providers/pair/pair-overview-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/search/index.ts delete mode 100644 packages/plugin-birdeye/src/providers/search/token-market-data-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/token/__tests__/token-overview-provider.test.ts delete mode 100644 packages/plugin-birdeye/src/providers/token/all-market-list-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/token/index.ts delete mode 100644 packages/plugin-birdeye/src/providers/token/new-listing-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/token/token-creation-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/token/token-holder-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/token/token-list-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/token/token-market-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/token/token-metadata-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/token/token-mint-burn-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/token/token-overview-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/token/token-security-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/token/token-trade-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/token/top-traders-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/token/trending-tokens-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/trader/gainers-losers-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/trader/index.ts delete mode 100644 packages/plugin-birdeye/src/providers/trader/trades-seek-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/utils.ts delete mode 100644 packages/plugin-birdeye/src/providers/wallet/index.ts delete mode 100644 packages/plugin-birdeye/src/providers/wallet/portfolio-multichain-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/wallet/supported-networks-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/wallet/token-balance-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/wallet/transaction-history-multichain-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/wallet/transaction-history-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/wallet/wallet-portfolio-provider.ts create mode 100644 packages/plugin-birdeye/src/services.ts create mode 100644 packages/plugin-birdeye/src/types/search-token.ts create mode 100644 packages/plugin-birdeye/src/types/shared.ts create mode 100644 packages/plugin-birdeye/src/types/token-metadata.ts create mode 100644 packages/plugin-birdeye/src/types/wallet.ts create mode 100644 packages/plugin-birdeye/src/utils.ts create mode 100644 src/providers/address-search.provider.ts diff --git a/packages/plugin-birdeye/src/actions/defi/get-ohlcv.ts b/packages/plugin-birdeye/src/actions/defi/get-ohlcv.ts index ea977ea98d..7300cc224f 100644 --- a/packages/plugin-birdeye/src/actions/defi/get-ohlcv.ts +++ b/packages/plugin-birdeye/src/actions/defi/get-ohlcv.ts @@ -9,16 +9,17 @@ import { Memory, State, } from "@elizaos/core"; +import { getTokenMetadata } from "../../services"; +import { BirdeyeChain } from "../../types/shared"; +import { TokenMetadataResponse } from "../../types/token-metadata"; import { BASE_URL, - Chain, extractChain, extractContractAddresses, formatTimestamp, formatValue, makeApiRequest, -} from "../../providers/utils"; -import { getTokenMetadata, TokenMetadataResponse } from "./get-token-metadata"; +} from "../../utils"; // eslint-disable-next-line @typescript-eslint/no-unused-vars const exampleResponse = { @@ -134,7 +135,7 @@ const formatVolume = (volume: number): string => { const getOHLCVData = async ( apiKey: string, contractAddress: string, - chain: Chain, + chain: BirdeyeChain, interval: TimeInterval = DEFAULT_INTERVAL ): Promise => { try { @@ -162,7 +163,7 @@ const getOHLCVData = async ( const formatOHLCVResponse = ( data: OHLCVResponse, tokenMetadata: TokenMetadataResponse | null, - chain: Chain, + chain: BirdeyeChain, interval: TimeInterval ): string => { const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); @@ -174,7 +175,7 @@ const formatOHLCVResponse = ( const { name, symbol, extensions } = tokenMetadata.data; tokenInfo = `${name} (${symbol})`; - const links = []; + const links: string[] = []; if (extensions.website) links.push(`[Website](${extensions.website})`); if (extensions.coingecko_id) links.push( @@ -306,8 +307,8 @@ export const getOHLCVAction: Action = { // First fetch token metadata const tokenMetadata = await getTokenMetadata( apiKey, - addresses[0], - chain + addresses[0].toString(), + chain as BirdeyeChain ); elizaLogger.info( @@ -316,8 +317,8 @@ export const getOHLCVAction: Action = { const ohlcvData = await getOHLCVData( apiKey, - addresses[0], - chain, + addresses[0].toString(), + chain as BirdeyeChain, interval ); @@ -331,7 +332,7 @@ export const getOHLCVAction: Action = { callbackData.text = formatOHLCVResponse( ohlcvData, tokenMetadata, - chain, + chain as BirdeyeChain, interval ); await callback(callbackData); diff --git a/packages/plugin-birdeye/src/actions/defi/get-price-history.ts b/packages/plugin-birdeye/src/actions/defi/get-price-history.ts index cc44b98f59..60ae76f0a0 100644 --- a/packages/plugin-birdeye/src/actions/defi/get-price-history.ts +++ b/packages/plugin-birdeye/src/actions/defi/get-price-history.ts @@ -9,18 +9,18 @@ import { Memory, State, } from "@elizaos/core"; +import { getTokenMetadata } from "../../services"; +import { BirdeyeChain } from "../../types/shared"; +import { TokenMetadataResponse } from "../../types/token-metadata"; import { BASE_URL, - Chain, extractChain, extractContractAddresses, extractTimeRange, formatTimestamp, formatValue, makeApiRequest, -} from "../../providers/utils"; -import { getTokenMetadata, TokenMetadataResponse } from "./get-token-metadata"; - +} from "../../utils"; // eslint-disable-next-line @typescript-eslint/no-unused-vars const exampleResponse = { success: true, @@ -137,7 +137,7 @@ const getPriceHistory = async ( contractAddress: string, startTime: number, endTime: number, - chain: Chain, + chain: BirdeyeChain, interval: TimeInterval = DEFAULT_INTERVAL ): Promise => { try { @@ -174,7 +174,7 @@ const formatPriceHistoryResponse = ( data: PriceHistoryResponse, tokenMetadata: TokenMetadataResponse | null, timeRange: { start: number; end: number }, - chain: Chain, + chain: BirdeyeChain, interval: TimeInterval ): string => { const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); @@ -188,7 +188,7 @@ const formatPriceHistoryResponse = ( const { name, symbol, extensions } = tokenMetadata.data; tokenInfo = `${name} (${symbol})`; - const links = []; + const links: string[] = []; if (extensions.website) links.push(`[Website](${extensions.website})`); if (extensions.coingecko_id) links.push( @@ -346,8 +346,8 @@ export const getPriceHistoryAction: Action = { // First fetch token metadata const tokenMetadata = await getTokenMetadata( apiKey, - addresses[0], - chain + addresses[0].toString(), + chain as BirdeyeChain ); elizaLogger.info( @@ -360,10 +360,10 @@ export const getPriceHistoryAction: Action = { const priceData = await getPriceHistory( apiKey, - addresses[0], + addresses[0].toString(), timeRange.start, timeRange.end, - chain, + chain as BirdeyeChain, interval ); @@ -378,7 +378,7 @@ export const getPriceHistoryAction: Action = { priceData, tokenMetadata, timeRange, - chain, + chain as BirdeyeChain, interval ); await callback(callbackData); diff --git a/packages/plugin-birdeye/src/actions/defi/get-token-metadata.ts b/packages/plugin-birdeye/src/actions/defi/get-token-metadata.ts index 705c22ee12..cd8cca6815 100644 --- a/packages/plugin-birdeye/src/actions/defi/get-token-metadata.ts +++ b/packages/plugin-birdeye/src/actions/defi/get-token-metadata.ts @@ -9,33 +9,13 @@ import { Memory, State, } from "@elizaos/core"; +import { BirdeyeChain } from "../../types/shared"; import { - BASE_URL, - Chain, + CHAIN_ALIASES, CHAIN_KEYWORDS, extractChain, extractContractAddresses, - makeApiRequest, -} from "../../providers/utils"; - -// Define explicit interface instead of using typeof -export interface TokenMetadataResponse { - data: { - address: string; - symbol: string; - name: string; - decimals: number; - extensions: { - coingecko_id?: string; - website?: string; - twitter?: string; - discord?: string; - medium?: string; - }; - logo_uri?: string; - }; - success: boolean; -} +} from "../../utils"; // Constants for keyword matching const METADATA_KEYWORDS = [ @@ -62,151 +42,6 @@ const containsMetadataKeyword = (text: string): boolean => { ); }; -export const getTokenMetadata = async ( - apiKey: string, - contractAddress: string, - chain: Chain -): Promise => { - try { - // Validate address format based on chain - const isValidAddress = (() => { - switch (chain) { - case "solana": - return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test( - contractAddress - ); - case "sui": - return /^0x[a-fA-F0-9]{64}$/i.test(contractAddress); - case "ethereum": - case "arbitrum": - case "avalanche": - case "bsc": - case "optimism": - case "polygon": - case "base": - case "zksync": - return /^0x[a-fA-F0-9]{40}$/i.test(contractAddress); - default: - return false; - } - })(); - - if (!isValidAddress) { - elizaLogger.error( - `Invalid address format for ${chain}: ${contractAddress}` - ); - return null; - } - - const params = new URLSearchParams({ - address: contractAddress, - }); - const url = `${BASE_URL}/defi/v3/token/meta-data/single?${params.toString()}`; - - elizaLogger.info( - `Fetching token metadata for ${contractAddress} on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { - apiKey, - chain, - }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching token metadata:", error.message); - } - return null; - } -}; - -const formatSocialLinks = (data: TokenMetadataResponse["data"]): string => { - const links = []; - const { extensions } = data; - - if (!extensions) { - return "No social links available"; - } - - if (extensions.website) { - links.push(`šŸŒ [Website](${extensions.website})`); - } - if (extensions.twitter) { - links.push(`šŸ¦ [Twitter](${extensions.twitter})`); - } - if (extensions.discord) { - links.push(`šŸ’¬ [Discord](${extensions.discord})`); - } - if (extensions.medium) { - links.push(`šŸ“ [Medium](${extensions.medium})`); - } - if (extensions.coingecko_id) { - links.push( - `šŸ¦Ž [CoinGecko](https://www.coingecko.com/en/coins/${extensions.coingecko_id})` - ); - } - - return links.length > 0 ? links.join("\n") : "No social links available"; -}; - -const formatMetadataResponse = ( - data: TokenMetadataResponse, - chain: Chain -): string => { - const tokenData = data.data; - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - const chainExplorer = (() => { - switch (chain) { - case "solana": - return `https://solscan.io/token/${tokenData.address}`; - case "ethereum": - return `https://etherscan.io/token/${tokenData.address}`; - case "arbitrum": - return `https://arbiscan.io/token/${tokenData.address}`; - case "avalanche": - return `https://snowtrace.io/token/${tokenData.address}`; - case "bsc": - return `https://bscscan.com/token/${tokenData.address}`; - case "optimism": - return `https://optimistic.etherscan.io/token/${tokenData.address}`; - case "polygon": - return `https://polygonscan.com/token/${tokenData.address}`; - case "base": - return `https://basescan.org/token/${tokenData.address}`; - case "zksync": - return `https://explorer.zksync.io/address/${tokenData.address}`; - case "sui": - return `https://suiscan.xyz/mainnet/object/${tokenData.address}`; - default: - return null; - } - })(); - - let response = `Token Metadata for ${tokenData.name} (${tokenData.symbol}) on ${chainName}\n\n`; - - // Basic Information - response += "šŸ“ Basic Information\n"; - response += `ā€¢ Name: ${tokenData.name}\n`; - response += `ā€¢ Symbol: ${tokenData.symbol}\n`; - response += `ā€¢ Address: ${tokenData.address}\n`; - response += `ā€¢ Decimals: ${tokenData.decimals}\n`; - if (chainExplorer) { - response += `ā€¢ Explorer: [View on ${chainName} Explorer](${chainExplorer})\n`; - } - - // Social Links - response += "\nšŸ”— Social Links & Extensions\n"; - response += formatSocialLinks(tokenData) + "\n"; - - // Logo - if (tokenData.logo_uri) { - response += "\nšŸ–¼ļø Logo\n"; - response += tokenData.logo_uri; - } - - return response; -}; - export const getTokenMetadataAction: Action = { name: "GET_TOKEN_METADATA", similes: [ @@ -257,69 +92,26 @@ export const getTokenMetadataAction: Action = { const addresses = extractContractAddresses(messageText); const chain = extractChain(messageText); - // Check if a specific chain was mentioned - const isChainMentioned = CHAIN_KEYWORDS.some((keyword) => - messageText.toLowerCase().includes(keyword.toLowerCase()) - ); + // Check if a specific chain was mentioned (including aliases) + const normalizedText = messageText.toLowerCase(); + const isChainMentioned = + CHAIN_KEYWORDS.some((keyword) => + normalizedText.includes(keyword.toLowerCase()) + ) || + Object.keys(CHAIN_ALIASES).some((alias) => + normalizedText.includes(alias.toLowerCase()) + ); if (addresses.length === 0) { callbackData.text = isChainMentioned - ? `I couldn't find a valid token address for ${chain} chain in your message. ${chain} addresses should match the format: ${getChainAddressFormat(chain)}` + ? `I couldn't find a valid token address for ${chain} chain in your message. ${chain} addresses should match the format: ${getChainAddressFormat( + chain as BirdeyeChain + )}` : "I couldn't find a valid token address in your message."; await callback(callbackData); return callbackData; } - // Validate that the address matches the specified chain format - const isValidForChain = (() => { - switch (chain) { - case "solana": - return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(addresses[0]); - case "sui": - return ( - /^0x[a-fA-F0-9]{64}$/i.test(addresses[0]) || - /^0x[a-fA-F0-9]{64}::[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$/i.test( - addresses[0] - ) - ); - case "ethereum": - case "arbitrum": - case "avalanche": - case "bsc": - case "optimism": - case "polygon": - case "base": - case "zksync": - return /^0x[a-fA-F0-9]{40}$/i.test(addresses[0]); - default: - return false; - } - })(); - - if (!isValidForChain && isChainMentioned) { - callbackData.text = `The provided address doesn't match the format for ${chain} chain. ${chain} addresses should match the format: ${getChainAddressFormat(chain)}`; - await callback(callbackData); - return callbackData; - } - - elizaLogger.info( - `TOKEN METADATA action activated for ${addresses[0]} on ${chain}` - ); - - const metadataData = await getTokenMetadata( - apiKey, - addresses[0], - chain - ); - - if (!metadataData) { - callbackData.text = - "I apologize, but I couldn't retrieve the token metadata at the moment."; - await callback(callbackData); - return callbackData; - } - - callbackData.text = formatMetadataResponse(metadataData, chain); await callback(callbackData); return callbackData; }) as Handler, @@ -372,7 +164,7 @@ export const getTokenMetadataAction: Action = { ] as ActionExample[][], }; -const getChainAddressFormat = (chain: Chain): string => { +const getChainAddressFormat = (chain: BirdeyeChain): string => { switch (chain) { case "solana": return "Base58 string (32-44 characters)"; diff --git a/packages/plugin-birdeye/src/actions/defi/get-token-trades.ts b/packages/plugin-birdeye/src/actions/defi/get-token-trades.ts deleted file mode 100644 index d7883cac20..0000000000 --- a/packages/plugin-birdeye/src/actions/defi/get-token-trades.ts +++ /dev/null @@ -1,446 +0,0 @@ -import { - Action, - ActionExample, - Content, - elizaLogger, - Handler, - HandlerCallback, - IAgentRuntime, - Memory, - State, -} from "@elizaos/core"; -import { - BASE_URL, - Chain, - extractChain, - extractContractAddresses, - extractLimit, - formatTimestamp, - formatValue, - makeApiRequest, - shortenAddress, -} from "../../providers/utils"; -import { getTokenMetadata, TokenMetadataResponse } from "./get-token-metadata"; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const exampleResponse = { - success: true, - data: { - items: [ - { - quote: { - symbol: "POTUS", - decimals: 6, - address: "7hyHfdgxwtaj1QpQSJw3s4R2LMxShPpmr2GsCw9npump", - amount: 13922809, - feeInfo: null, - uiAmount: 13.922809, - price: null, - nearestPrice: 0.00008581853084042329, - changeAmount: 13922809, - uiChangeAmount: 13.922809, - }, - base: { - symbol: "SOL", - decimals: 9, - address: "So11111111111111111111111111111111111111112", - amount: 9090, - uiAmount: 0.00000909, - price: null, - nearestPrice: 128.201119598627, - changeAmount: -9090, - uiChangeAmount: -0.00000909, - }, - basePrice: null, - quotePrice: null, - txHash: "5G72183B77KafzKv4GEJNnDrV7rtspv5X5WM9yG9g1P89iG1UCGBuAqcMasgGhRYN24bmWsNPkQqptRbX5uoH44K", - source: "raydium", - blockUnixTime: 1726676178, - txType: "swap", - owner: "AavgaV4YKned3RN6JVMANKmAaVS2Tpfnw88HbYtzgBAn", - side: "sell", - alias: null, - pricePair: 1531662.1562156219, - from: { - symbol: "SOL", - decimals: 9, - address: "So11111111111111111111111111111111111111112", - amount: 9090, - uiAmount: 0.00000909, - price: null, - nearestPrice: 128.201119598627, - changeAmount: -9090, - uiChangeAmount: -0.00000909, - }, - to: { - symbol: "POTUS", - decimals: 6, - address: "7hyHfdgxwtaj1QpQSJw3s4R2LMxShPpmr2GsCw9npump", - amount: 13922809, - feeInfo: null, - uiAmount: 13.922809, - price: null, - nearestPrice: 0.00008581853084042329, - changeAmount: 13922809, - uiChangeAmount: 13.922809, - }, - tokenPrice: null, - poolId: "2L8fo6g6me9ZubZhH2iiz6616GouRbGeEuvNoGv69xWE", - }, - { - quote: { - symbol: "PEAKY", - decimals: 6, - address: "62uBW5K24PdxXk185tNjz9pwzkpHinKt8qZznxPPpump", - amount: 35238136, - feeInfo: null, - uiAmount: 35.238136, - price: null, - nearestPrice: 0.00003742796781669965, - changeAmount: 35238136, - uiChangeAmount: 35.238136, - }, - base: { - symbol: "SOL", - decimals: 9, - address: "So11111111111111111111111111111111111111112", - amount: 10333, - uiAmount: 0.000010333, - price: null, - nearestPrice: 128.201119598627, - changeAmount: -10333, - uiChangeAmount: -0.000010333, - }, - basePrice: null, - quotePrice: null, - txHash: "zXdSLDTX4MVzunVJgFbJmiLY9z2hZ3n28w6bYvKn1aZVL1QZGozkyMMMteFqpyWraUTdRyX1GKFnJYkqPsL5SJK", - source: "raydium", - blockUnixTime: 1726676178, - txType: "swap", - owner: "CDt3xtwPVWDbhENL3QhDX5XYVx9JNCvewfdfCRy1cKFt", - side: "sell", - alias: null, - pricePair: 3410252.2016839255, - from: { - symbol: "SOL", - decimals: 9, - address: "So11111111111111111111111111111111111111112", - amount: 10333, - uiAmount: 0.000010333, - price: null, - nearestPrice: 128.201119598627, - changeAmount: -10333, - uiChangeAmount: -0.000010333, - }, - to: { - symbol: "PEAKY", - decimals: 6, - address: "62uBW5K24PdxXk185tNjz9pwzkpHinKt8qZznxPPpump", - amount: 35238136, - feeInfo: null, - uiAmount: 35.238136, - price: null, - nearestPrice: 0.00003742796781669965, - changeAmount: 35238136, - uiChangeAmount: 35.238136, - }, - tokenPrice: null, - poolId: "5vsk6iYjKXEo6x7maZJwh36UjqwFxkRtoHK5Nphh3ht1", - }, - ], - hasNext: true, - }, -}; - -type TokenTradesResponse = typeof exampleResponse; - -// Constants for keyword matching -const TOKEN_TRADES_KEYWORDS = [ - "token trades", - "token swaps", - "token transactions", - "token activity", - "token orders", - "token executions", - "token trading", - "token market activity", - "token exchange activity", - "token trading history", - "token market history", - "token exchange history", -] as const; - -// Helper function to check if text contains trades-related keywords -const containsTokenTradesKeyword = (text: string): boolean => { - return TOKEN_TRADES_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getTokenTrades = async ( - apiKey: string, - contractAddress: string, - chain: Chain, - limit: number -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - limit: limit.toString(), - }); - const url = `${BASE_URL}/defi/trades_token?${params.toString()}`; - - elizaLogger.info( - `Fetching token trades for ${contractAddress} on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { - apiKey, - chain, - }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching token trades:", error.message); - } - return null; - } -}; - -const formatTrade = ( - trade: TokenTradesResponse["data"]["items"][0] -): string => { - const timestamp = formatTimestamp(trade.blockUnixTime); - const side = trade.side === "buy" ? "šŸŸ¢ Buy" : "šŸ”“ Sell"; - const baseAmount = formatValue(trade.base.uiAmount); - const quoteAmount = formatValue(trade.quote.uiAmount); - - let response = `${side} - ${timestamp}\n`; - response += `ā€¢ ${baseAmount} ${trade.base.symbol} ā‡„ ${quoteAmount} ${trade.quote.symbol}\n`; - response += `ā€¢ Source: ${trade.source}\n`; - response += `ā€¢ Owner: ${shortenAddress(trade.owner)}\n`; - response += `ā€¢ Tx: ${shortenAddress(trade.txHash)}`; - - return response; -}; - -const formatTokenTradesResponse = ( - data: TokenTradesResponse, - tokenMetadata: TokenMetadataResponse | null, - chain: Chain -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - - let tokenInfo = "Unknown Token"; - let tokenLinks = ""; - - if (tokenMetadata?.success) { - const { name, symbol, extensions } = tokenMetadata.data; - tokenInfo = `${name} (${symbol})`; - - const links = []; - if (extensions.website) links.push(`[Website](${extensions.website})`); - if (extensions.coingecko_id) - links.push( - `[CoinGecko](https://www.coingecko.com/en/coins/${extensions.coingecko_id})` - ); - if (links.length > 0) { - tokenLinks = `\nšŸ“Œ More Information: ${links.join(" ā€¢ ")}`; - } - } - - let response = `Recent Trades for ${tokenInfo} on ${chainName}${tokenLinks}\n\n`; - - if (!data.success || !data.data.items || data.data.items.length === 0) { - return response + "No trades found."; - } - - const trades = data.data.items; - - // Calculate summary statistics - const buyTrades = trades.filter((t) => t.side === "buy"); - const buyRatio = (buyTrades.length / trades.length) * 100; - - const baseVolume = trades.reduce( - (sum, t) => sum + Math.abs(t.base.uiAmount), - 0 - ); - const quoteVolume = trades.reduce( - (sum, t) => sum + Math.abs(t.quote.uiAmount), - 0 - ); - const averageBaseAmount = baseVolume / trades.length; - const averageQuoteAmount = quoteVolume / trades.length; - - response += `šŸ“Š Summary\n`; - response += `ā€¢ Total Trades: ${trades.length}\n`; - response += `ā€¢ Buy/Sell Ratio: ${buyRatio.toFixed(1)}% buys\n`; - response += `ā€¢ Total Volume: ${formatValue(baseVolume)} ${trades[0].base.symbol}\n`; - response += `ā€¢ Average Trade Size: ${formatValue(averageBaseAmount)} ${trades[0].base.symbol}\n`; - response += `ā€¢ Total Quote Volume: ${formatValue(quoteVolume)} ${trades[0].quote.symbol}\n`; - response += `ā€¢ Average Quote Size: ${formatValue(averageQuoteAmount)} ${trades[0].quote.symbol}\n\n`; - - // Add market analysis - const tradeFrequency = - trades.length > 20 ? "high" : trades.length > 10 ? "moderate" : "low"; - const volumeLevel = - baseVolume > averageBaseAmount * 2 - ? "high" - : baseVolume > averageBaseAmount - ? "moderate" - : "low"; - const marketAnalysis = `Market shows ${tradeFrequency} trading activity with ${volumeLevel} volume per trade.`; - - response += `šŸ“ˆ Market Analysis\n`; - response += `ā€¢ ${marketAnalysis}\n\n`; - - response += `šŸ”„ Recent Trades\n`; - trades.forEach((trade, index) => { - response += `${index + 1}. ${formatTrade(trade)}\n\n`; - }); - - if (data.data.hasNext) { - response += `Note: More trades are available. This is a limited view of the most recent activity.`; - } - - return response; -}; - -export const getTokenTradesAction: Action = { - name: "GET_TOKEN_TRADES", - similes: [ - "SHOW_TOKEN_TRADES", - "VIEW_TOKEN_TRADES", - "CHECK_TOKEN_TRADES", - "DISPLAY_TOKEN_TRADES", - "GET_TRADE_HISTORY", - "SHOW_TRADE_HISTORY", - "VIEW_TRADING_ACTIVITY", - "CHECK_MARKET_ACTIVITY", - "TOKEN_TRADING_HISTORY", - "TOKEN_MARKET_ACTIVITY", - ], - description: - "Retrieve and analyze recent trading activity for a token, including trade details, volume statistics, and market analysis.", - validate: async ( - _runtime: IAgentRuntime, - message: Memory, - _state: State | undefined - ): Promise => { - return containsTokenTradesKeyword(message.content.text); - }, - handler: (async ( - runtime: IAgentRuntime, - message: Memory, - _state: State | undefined, - _options: any, - callback: HandlerCallback - ): Promise => { - const callbackData: Content = { - text: "", - action: "GET_TOKEN_TRADES_RESPONSE", - source: message.content.source, - }; - - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - callbackData.text = - "I'm unable to fetch the token trades due to missing API credentials."; - await callback(callbackData); - return callbackData; - } - - const messageText = message.content.text; - const addresses = extractContractAddresses(messageText); - if (addresses.length === 0) { - callbackData.text = - "I couldn't find a valid token address in your message."; - await callback(callbackData); - return callbackData; - } - - const chain = extractChain(messageText); - const limit = extractLimit(messageText); - - // First fetch token metadata - const tokenMetadata = await getTokenMetadata( - apiKey, - addresses[0], - chain - ); - - elizaLogger.info( - `TOKEN TRADES action activated for ${addresses[0]} on ${chain}` - ); - - const tradesData = await getTokenTrades( - apiKey, - addresses[0], - chain, - limit - ); - - if (!tradesData) { - callbackData.text = - "I apologize, but I couldn't retrieve the token trades at the moment."; - await callback(callbackData); - return callbackData; - } - - callbackData.text = formatTokenTradesResponse( - tradesData, - tokenMetadata, - chain - ); - await callback(callbackData); - return callbackData; - }) as Handler, - examples: [ - [ - { - user: "{{user1}}", - content: { - text: "Show me recent trades for token 0x1234... on Ethereum", - }, - }, - { - user: "{{user2}}", - content: { - text: "Here's the detailed trading activity analysis including recent trades, volume statistics, and market insights.", - action: "GET_TOKEN_TRADES", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { - text: "What's the trading activity for ABC123... on Solana?", - }, - }, - { - user: "{{user2}}", - content: { - text: "I'll analyze the recent trading activity and provide you with a comprehensive overview of the market.", - action: "GET_TOKEN_TRADES", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { - text: "Get me the last 20 trades for token XYZ... on BSC", - }, - }, - { - user: "{{user2}}", - content: { - text: "I'll fetch the recent trades and provide detailed statistics about the trading activity.", - action: "GET_TOKEN_TRADES", - }, - }, - ], - ] as ActionExample[][], -}; diff --git a/packages/plugin-birdeye/src/actions/defi/get-supported-networks.ts b/packages/plugin-birdeye/src/actions/defi/networks.ts similarity index 72% rename from packages/plugin-birdeye/src/actions/defi/get-supported-networks.ts rename to packages/plugin-birdeye/src/actions/defi/networks.ts index 0f476e9fd8..415d9f2080 100644 --- a/packages/plugin-birdeye/src/actions/defi/get-supported-networks.ts +++ b/packages/plugin-birdeye/src/actions/defi/networks.ts @@ -2,54 +2,15 @@ import { Action, ActionExample, Content, + elizaLogger, Handler, HandlerCallback, IAgentRuntime, Memory, State, - elizaLogger, } from "@elizaos/core"; -import { BASE_URL, makeApiRequest } from "../../providers/utils"; - -// Constants for keyword matching -const NETWORK_KEYWORDS = [ - "supported networks", - "available networks", - "supported chains", - "available chains", - "which networks", - "which chains", - "list networks", - "list chains", - "show networks", - "show chains", - "network support", - "chain support", -] as const; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const exampleResponse = { - success: true, - data: [ - "solana", - "ethereum", - "arbitrum", - "avalanche", - "bsc", - "optimism", - "polygon", - "base", - "zksync", - "sui", - ], -}; - -// Helper function to check if text contains network-related keywords -const containsNetworkKeyword = (text: string): boolean => { - return NETWORK_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; +import { BASE_URL, makeApiRequest } from "../../utils.ts"; +import { containsNetworkKeyword, NetworksResponse } from "./networks.utils.ts"; export const getSupportedNetworksAction: Action = { name: "GET_SUPPORTED_NETWORKS", @@ -97,7 +58,7 @@ export const getSupportedNetworksAction: Action = { elizaLogger.info("Fetching supported networks"); const url = `${BASE_URL}/defi/networks`; - const networksData = await makeApiRequest(url, { + const networksData = await makeApiRequest(url, { apiKey, }); @@ -108,7 +69,7 @@ export const getSupportedNetworksAction: Action = { return callbackData; } - callbackData.text = `Currently supported networks for information about tokens, swaps, prices, gainers and losers are: ${networksData.data.join(", ")}`; + callbackData.text = `Currently supported networks are: ${networksData.data.join(", ")}`; await callback(callbackData); return callbackData; }) as Handler, diff --git a/packages/plugin-birdeye/src/actions/defi/networks.utils.ts b/packages/plugin-birdeye/src/actions/defi/networks.utils.ts new file mode 100644 index 0000000000..eefe89597c --- /dev/null +++ b/packages/plugin-birdeye/src/actions/defi/networks.utils.ts @@ -0,0 +1,27 @@ +export interface NetworksResponse { + success: boolean; + data: string[]; +} + +// Constants for keyword matching +export const NETWORK_KEYWORDS = [ + "supported networks", + "available networks", + "supported chains", + "available chains", + "which networks", + "which chains", + "list networks", + "list chains", + "show networks", + "show chains", + "network support", + "chain support", +] as const; + +// Helper function to check if text contains network-related keywords +export const containsNetworkKeyword = (text: string): boolean => { + return NETWORK_KEYWORDS.some((keyword) => + text.toLowerCase().includes(keyword.toLowerCase()) + ); +}; diff --git a/packages/plugin-birdeye/src/index.ts b/packages/plugin-birdeye/src/index.ts index f686940ea5..e997bfafb3 100644 --- a/packages/plugin-birdeye/src/index.ts +++ b/packages/plugin-birdeye/src/index.ts @@ -1,46 +1,19 @@ import { Plugin } from "@elizaos/core"; -import { getOHLCVAction } from "./actions/defi/get-ohlcv"; -import { getPriceHistoryAction } from "./actions/defi/get-price-history"; -import { getSupportedNetworksAction } from "./actions/defi/get-supported-networks"; -import { getTokenMetadataAction } from "./actions/defi/get-token-metadata"; -import { getTokenTradesAction } from "./actions/defi/get-token-trades"; +import { getSupportedNetworksAction } from "./actions/defi/networks"; +import { addressSearchProvider } from "./providers/address-search-provider"; export const birdeyePlugin: Plugin = { name: "birdeye", description: "Birdeye Plugin for token data and analytics", actions: [ getSupportedNetworksAction, - getTokenMetadataAction, - getPriceHistoryAction, - getOHLCVAction, - getTokenTradesAction, + // getTokenMetadataAction, + // getPriceHistoryAction, + // getOHLCVAction, + // getTokenTradesAction, ], evaluators: [], - providers: [ - // networksProvider, - // // DeFi providers - // priceProvider, - // priceMultipleProvider, - // ohlcvProvider, - // priceVolumeProvider, - // // Pair providers - // pairOverviewProvider, - // // Search providers - // tokenMarketDataProvider, - // // Token providers - // tokenOverviewProvider, - // tokenSecurityProvider, - // tokenListProvider, - // trendingTokensProvider, - // tokenCreationProvider, - // tokenTradeProvider, - // // Trader providers - // gainersLosersProvider, - // tradesSeekProvider, - // // Wallet providers - // transactionHistoryProvider, - // walletPortfolioProvider, - ], + providers: [addressSearchProvider], }; export default birdeyePlugin; diff --git a/packages/plugin-birdeye/src/providers/__tests__/utils.test.ts b/packages/plugin-birdeye/src/providers/__tests__/utils.test.ts deleted file mode 100644 index cdc8922733..0000000000 --- a/packages/plugin-birdeye/src/providers/__tests__/utils.test.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { - extractChain, - extractContractAddresses, - extractLimit, - extractTimeframe, - extractTimeRange, - formatPercentChange, - formatPrice, - formatTimestamp, - formatValue, - shortenAddress, - TIME_UNITS, -} from "../utils"; - -describe("Chain Extraction", () => { - test("extracts chain from text correctly", () => { - expect(extractChain("Check price on Solana")).toBe("solana"); - expect(extractChain("Look up Ethereum token")).toBe("ethereum"); - expect(extractChain("No chain mentioned")).toBe("solana"); // default - }); -}); - -describe("Contract Address Extraction", () => { - test("extracts Ethereum addresses correctly", () => { - const text = - "Token address is 0x1234567890123456789012345678901234567890"; - expect(extractContractAddresses(text)).toEqual([ - "0x1234567890123456789012345678901234567890", - ]); - }); - - test("extracts Solana addresses correctly", () => { - const text = - "Token address is TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; - expect(extractContractAddresses(text)).toEqual([ - "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", - ]); - }); - - test("extracts multiple addresses correctly", () => { - const text = - "0x1234567890123456789012345678901234567890 TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; - expect(extractContractAddresses(text)).toHaveLength(2); - }); -}); - -describe("Timeframe Extraction", () => { - test("extracts explicit timeframes correctly", () => { - expect(extractTimeframe("Show 1h chart")).toBe("1h"); - expect(extractTimeframe("Display 15m data")).toBe("15m"); - expect(extractTimeframe("Get 1d overview")).toBe("1d"); - }); - - test("extracts semantic timeframes correctly", () => { - expect(extractTimeframe("Show short term analysis")).toBe("15m"); - expect(extractTimeframe("Get medium term view")).toBe("1h"); - expect(extractTimeframe("Display long term data")).toBe("1d"); - }); - - test("returns default timeframe for unclear input", () => { - expect(extractTimeframe("Show me the data")).toBe("1h"); - }); -}); - -describe("Time Range Extraction", () => { - beforeEach(() => { - jest.useFakeTimers(); - jest.setSystemTime(new Date("2024-01-01T00:00:00Z")); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - test("extracts specific date ranges", () => { - const result = extractTimeRange("from 2023-12-01 to 2023-12-31"); - expect(result.start).toBe(new Date("2023-12-01").getTime() / 1000); - expect(result.end).toBe(new Date("2023-12-31").getTime() / 1000); - }); - - test("extracts relative time ranges", () => { - const now = Math.floor(Date.now() / 1000); - const result = extractTimeRange("24 hours ago"); - expect(result.end).toBe(now); - expect(result.start).toBe(now - TIME_UNITS.day); - }); - - test("handles semantic time ranges", () => { - const now = Math.floor(Date.now() / 1000); - const result = extractTimeRange("show me today's data"); - expect(result.end).toBe(now); - expect(result.start).toBe(now - TIME_UNITS.day); - }); -}); - -describe("Limit Extraction", () => { - test("extracts explicit limits", () => { - expect(extractLimit("show 20 results")).toBe(20); - expect(extractLimit("display 5 items")).toBe(5); - expect(extractLimit("fetch 200 records")).toBe(100); // clamped to max - }); - - test("extracts semantic limits", () => { - expect(extractLimit("show me everything")).toBe(100); - expect(extractLimit("give me a brief overview")).toBe(5); - expect(extractLimit("provide detailed analysis")).toBe(50); - }); - - test("returns default limit for unclear input", () => { - expect(extractLimit("show me the data")).toBe(10); - }); -}); - -describe("Formatting Functions", () => { - test("formats values correctly", () => { - expect(formatValue(1500000000)).toBe("$1.50B"); - expect(formatValue(1500000)).toBe("$1.50M"); - expect(formatValue(1500)).toBe("$1.50K"); - expect(formatValue(150)).toBe("$150.00"); - }); - - test("formats percent changes correctly", () => { - expect(formatPercentChange(10.5)).toBe("šŸ“ˆ 10.50%"); - expect(formatPercentChange(-5.25)).toBe("šŸ“‰ 5.25%"); - expect(formatPercentChange(undefined)).toBe("N/A"); - }); - - test("shortens addresses correctly", () => { - expect( - shortenAddress("0x1234567890123456789012345678901234567890") - ).toBe("0x1234...7890"); - expect(shortenAddress("short")).toBe("short"); - expect(shortenAddress("")).toBe("Unknown"); - }); - - test("formats timestamps correctly", () => { - const timestamp = 1704067200; // 2024-01-01 00:00:00 UTC - expect(formatTimestamp(timestamp)).toMatch(/2024/); - }); - - test("formats prices correctly", () => { - expect(formatPrice(123.456)).toBe("123.46"); - expect(formatPrice(0.000123)).toBe("1.23e-4"); - }); -}); diff --git a/packages/plugin-birdeye/src/providers/address-search-provider.ts b/packages/plugin-birdeye/src/providers/address-search-provider.ts new file mode 100644 index 0000000000..40f178a762 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/address-search-provider.ts @@ -0,0 +1,111 @@ +import { + elizaLogger, + IAgentRuntime, + Memory, + Provider, + State, +} from "@elizaos/core"; +import { getTokenMetadata, searchTokens } from "../services"; +import { + extractChain, + extractContractAddresses, + extractSymbols, + formatTokenInfo, +} from "../utils"; + +/** + * Searches message text for contract addresses, symbols, or wallet addresses and enriches them with: + * - Portfolio data if its a wallet address + * - Token metadata if its a contract address or symbol + */ +export const addressSearchProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); + if (!apiKey) { + return null; + } + + const messageText = message.content.text; + + // STEP 1 - Extract addresses and symbols + const addresses = extractContractAddresses(messageText); + const symbols = extractSymbols(messageText); + + if (addresses.length === 0 && symbols.length === 0) return null; + + elizaLogger.info( + `Searching Birdeye provider for ${addresses.length} addresses and ${symbols.length} symbols` + ); + + // STEP 2 - Search Birdeye services for token matches based on addresses and symbols + + // Search in parallel for all terms + const searchAddressesForTokenMatch = addresses.map((address) => + searchTokens(apiKey, { + keyword: address.address, + limit: 1, + }).then((results) => ({ + searchTerm: address.address, + address: address.address, + // find the result that matches the address + result: + results.find((r) => r.address === address.address) || null, + })) + ); + + // Search in parallel for all terms + const searchSymbolsForTokenMatch = symbols.map((symbol) => + searchTokens(apiKey, { + keyword: symbol, + limit: 1, + }).then((results) => ({ + searchTerm: symbol, + symbol: results[0]?.symbol || null, + address: results[0]?.address || null, + // find the result that matches the symbol + result: results.find((r) => r.symbol === symbol) || null, + })) + ); + + const results = await Promise.all([ + ...searchAddressesForTokenMatch, + ...searchSymbolsForTokenMatch, + ]); + const validResults = results.filter((r) => r.result !== null); + + // bail if no valid results + if (validResults.length === 0) return null; + + // for each result, get the chain from the search term + const resultsWithChains = validResults.map( + ({ searchTerm, address }) => ({ + searchTerm, + address, + chain: extractChain(address), + }) + ); + + // STEP 3 - get metadata for all valid results and format them. This includes additional token information like social links, logo, etc. + const resultsWithMetadata = await Promise.all( + resultsWithChains.map(({ address, chain }) => + getTokenMetadata(apiKey, address, chain) + ) + ); + + // STEP 4 - Format all results together + const completeResults = `The following data is available for the symbols and contract addresses requested: ${validResults + .map( + ({ searchTerm, result }, index) => + `Search term "${searchTerm}":\n${formatTokenInfo(result!, resultsWithMetadata[index])}` + ) + .join("\n\n")}`; + + console.log(completeResults); + + return completeResults; + }, +}; diff --git a/packages/plugin-birdeye/src/providers/defi/__tests__/price-provider.test.ts b/packages/plugin-birdeye/src/providers/defi/__tests__/price-provider.test.ts deleted file mode 100644 index 5c364f5848..0000000000 --- a/packages/plugin-birdeye/src/providers/defi/__tests__/price-provider.test.ts +++ /dev/null @@ -1,218 +0,0 @@ -import { IAgentRuntime, Memory, State } from "@elizaos/core"; -import { priceProvider } from "../price-provider"; - -// Mock data -const mockPriceData = { - price: 1.23, - timestamp: 1704067200, // 2024-01-01 00:00:00 UTC - token: "TEST", - priceChange24h: 0.05, - priceChange24hPercent: 4.23, -}; - -// Mock fetch globally -global.fetch = jest.fn(); - -describe("Price Provider", () => { - let mockRuntime: IAgentRuntime; - let mockMessage: Memory; - let mockState: State; - - beforeEach(() => { - // Reset mocks - jest.clearAllMocks(); - - // Mock runtime - mockRuntime = { - getSetting: jest.fn().mockReturnValue("mock-api-key"), - } as unknown as IAgentRuntime; - - // Mock message - mockMessage = { - content: { - text: "What is the price of 0x1234567890123456789012345678901234567890 on ethereum", - }, - } as Memory; - - // Mock state - mockState = {} as State; - - // Mock successful fetch response - (global.fetch as jest.Mock).mockResolvedValue({ - ok: true, - json: async () => ({ data: mockPriceData }), - }); - }); - - test("returns null when API key is missing", async () => { - (mockRuntime.getSetting as jest.Mock).mockReturnValue(null); - const result = await priceProvider.get( - mockRuntime, - mockMessage, - mockState - ); - expect(result).toBeNull(); - }); - - test("returns null when message does not contain price keywords", async () => { - mockMessage.content.text = "random message without price keywords"; - const result = await priceProvider.get( - mockRuntime, - mockMessage, - mockState - ); - expect(result).toBeNull(); - }); - - test("returns null when no contract address is found", async () => { - mockMessage.content.text = "what is the price of invalid-address"; - const result = await priceProvider.get( - mockRuntime, - mockMessage, - mockState - ); - expect(result).toBeNull(); - }); - - test("handles API error gracefully", async () => { - (global.fetch as jest.Mock).mockRejectedValue(new Error("API Error")); - const result = await priceProvider.get( - mockRuntime, - mockMessage, - mockState - ); - expect(result).toBeNull(); - }); - - test("handles 404 response gracefully", async () => { - (global.fetch as jest.Mock).mockResolvedValue({ - ok: false, - status: 404, - }); - const result = await priceProvider.get( - mockRuntime, - mockMessage, - mockState - ); - expect(result).toBeNull(); - }); - - test("formats price response correctly with all data", async () => { - const result = await priceProvider.get( - mockRuntime, - mockMessage, - mockState - ); - - expect(result).toContain( - `Price for ${mockPriceData.token} on Ethereum` - ); - expect(result).toContain( - `Current Price: $${mockPriceData.price.toFixed(2)}` - ); - expect(result).toContain( - `24h Change: $${mockPriceData.priceChange24h.toFixed(2)}` - ); - expect(result).toContain( - `24h Change %: ${mockPriceData.priceChange24hPercent.toFixed(2)}%` - ); - expect(result).toContain("Last Updated:"); - }); - - test("formats price response correctly with minimal data", async () => { - const minimalPriceData = { - price: 0.000123, - timestamp: 1704067200, - token: "TEST", - }; - - (global.fetch as jest.Mock).mockResolvedValue({ - ok: true, - json: async () => ({ data: minimalPriceData }), - }); - - const result = await priceProvider.get( - mockRuntime, - mockMessage, - mockState - ); - - expect(result).toContain( - `Price for ${minimalPriceData.token} on Ethereum` - ); - expect(result).toContain("Current Price: $1.23e-4"); // Scientific notation for small numbers - expect(result).not.toContain("24h Change:"); - expect(result).not.toContain("24h Change %:"); - expect(result).toContain("Last Updated:"); - }); - - test("extracts chain correctly", async () => { - mockMessage.content.text = - "what is the price of 0x1234567890123456789012345678901234567890 on ethereum"; - await priceProvider.get(mockRuntime, mockMessage, mockState); - - expect(global.fetch).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ - headers: expect.objectContaining({ - "x-chain": "ethereum", - }), - }) - ); - }); - - test("defaults to solana chain when not specified", async () => { - mockMessage.content.text = - "what is the price of 0x1234567890123456789012345678901234567890"; - await priceProvider.get(mockRuntime, mockMessage, mockState); - - expect(global.fetch).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ - headers: expect.objectContaining({ - "x-chain": "solana", - }), - }) - ); - }); - - test("recognizes various price keywords", async () => { - const priceKeywords = [ - "price", - "cost", - "worth", - "value", - "rate", - "quote", - "how much", - ]; - - for (const keyword of priceKeywords) { - mockMessage.content.text = `${keyword} of 0x1234567890123456789012345678901234567890`; - const result = await priceProvider.get( - mockRuntime, - mockMessage, - mockState - ); - expect(result).not.toBeNull(); - } - }); - - test("handles different address formats", async () => { - // Test Ethereum address - mockMessage.content.text = - "price of 0x1234567890123456789012345678901234567890"; - let result = await priceProvider.get( - mockRuntime, - mockMessage, - mockState - ); - expect(result).not.toBeNull(); - - // Test Solana address - mockMessage.content.text = - "price of TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; - result = await priceProvider.get(mockRuntime, mockMessage, mockState); - expect(result).not.toBeNull(); - }); -}); diff --git a/packages/plugin-birdeye/src/providers/defi/index.ts b/packages/plugin-birdeye/src/providers/defi/index.ts deleted file mode 100644 index af593176d4..0000000000 --- a/packages/plugin-birdeye/src/providers/defi/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -export * from "./networks-provider"; -export * from "./ohlcv-base-quote-provider"; -export * from "./ohlcv-pair-provider"; -export * from "./ohlcv-provider"; -export * from "./pair-trades-provider"; -export * from "./pair-trades-seek-provider"; -export * from "./price-history-provider"; -export * from "./price-multiple-provider"; -export * from "./price-provider"; -export * from "./price-volume-provider"; -export * from "./token-trades-provider"; -export * from "./trades-seek-provider"; diff --git a/packages/plugin-birdeye/src/providers/defi/networks-provider.ts b/packages/plugin-birdeye/src/providers/defi/networks-provider.ts deleted file mode 100644 index a13659bc3c..0000000000 --- a/packages/plugin-birdeye/src/providers/defi/networks-provider.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { BASE_URL, makeApiRequest } from "../utils"; - -// Constants -const NETWORK_KEYWORDS = [ - "supported networks", - "available networks", - "supported chains", - "available chains", - "which networks", - "which chains", - "list networks", - "list chains", - "show networks", - "show chains", - "network support", - "chain support", -] as const; - -// Helper functions -const containsNetworkKeyword = (text: string): boolean => { - return NETWORK_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -// use sample response to simplify type generation -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const sampleResponse = { - data: [ - "solana", - "ethereum", - "arbitrum", - "avalanche", - "bsc", - "optimism", - "polygon", - "base", - "zksync", - "sui", - ], - success: true, -}; - -export const networksProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsNetworkKeyword(messageText)) { - return null; - } - - elizaLogger.info("NETWORKS provider activated"); - - const url = `${BASE_URL}/defi/networks`; - - elizaLogger.info("Fetching supported networks from:", url); - - const networksData = await makeApiRequest(url, { - apiKey, - }); - - console.log(JSON.stringify(networksData, null, 2)); - - if (!networksData) { - return null; - } - - return `Currently supported networks for information about tokens, swaps, prices, gainers and losers are: ${networksData.data.join(", ")}`; - }, -}; diff --git a/packages/plugin-birdeye/src/providers/defi/ohlcv-base-quote-provider.ts b/packages/plugin-birdeye/src/providers/defi/ohlcv-base-quote-provider.ts deleted file mode 100644 index 0a07937ade..0000000000 --- a/packages/plugin-birdeye/src/providers/defi/ohlcv-base-quote-provider.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { - BASE_URL, - Chain, - Timeframe, - extractChain, - extractContractAddresses, - extractLimit, - extractTimeRange, - extractTimeframe, - formatTimestamp, - formatValue, - makeApiRequest, -} from "../utils"; - -// Types -interface OHLCVData { - timestamp: number; - open: number; - high: number; - low: number; - close: number; - volume: number; -} - -interface BaseQuoteOHLCVResponse { - data: OHLCVData[]; - pair: { - baseToken: string; - quoteToken: string; - }; -} - -// Constants -const BASE_QUOTE_OHLCV_KEYWORDS = [ - "base quote ohlcv", - "base quote candlestick", - "base quote candles", - "base quote chart", - "base quote price history", - "base quote historical data", - "base quote market data", - "base quote trading data", - "base/quote chart", - "base/quote price", - "base/quote history", - "base/quote movement", - "token pair chart", - "token pair price", - "token pair history", - "token pair movement", -] as const; - -// Helper functions -const containsBaseQuoteOHLCVKeyword = (text: string): boolean => { - return BASE_QUOTE_OHLCV_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getBaseQuoteOHLCV = async ( - apiKey: string, - baseAddress: string, - quoteAddress: string, - timeframe: Timeframe, - chain: Chain, - limit: number -): Promise => { - try { - const params = new URLSearchParams({ - base_address: baseAddress, - quote_address: quoteAddress, - timeframe, - limit: limit.toString(), - }); - const url = `${BASE_URL}/defi/ohlcv_base_quote?${params.toString()}`; - - elizaLogger.info( - `Fetching base/quote OHLCV data for ${baseAddress}/${quoteAddress} with ${timeframe} timeframe on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { - apiKey, - chain, - }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error( - "Error fetching base/quote OHLCV data:", - error.message - ); - } - return null; - } -}; - -const formatOHLCVData = (data: OHLCVData): string => { - const timestamp = formatTimestamp(data.timestamp); - const change = ((data.close - data.open) / data.open) * 100; - const trend = change >= 0 ? "šŸŸ¢" : "šŸ”“"; - - let response = `${trend} ${timestamp}\n`; - response += `ā€¢ Open: ${formatValue(data.open)}\n`; - response += `ā€¢ High: ${formatValue(data.high)}\n`; - response += `ā€¢ Low: ${formatValue(data.low)}\n`; - response += `ā€¢ Close: ${formatValue(data.close)}\n`; - response += `ā€¢ Volume: ${formatValue(data.volume)}\n`; - response += `ā€¢ Change: ${change >= 0 ? "+" : ""}${change.toFixed(2)}%`; - - return response; -}; - -const formatBaseQuoteOHLCVResponse = ( - data: BaseQuoteOHLCVResponse, - timeframe: Timeframe, - chain: Chain, - timeRange: { start: number; end: number } -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - const startDate = formatTimestamp(timeRange.start); - const endDate = formatTimestamp(timeRange.end); - - let response = `Base/Quote OHLCV Data for ${data.pair.baseToken}/${data.pair.quoteToken} on ${chainName}\n`; - response += `Timeframe: ${timeframe} (${startDate} to ${endDate})\n\n`; - - if (data.data.length === 0) { - return response + "No OHLCV data found for this base/quote pair."; - } - - // Calculate summary statistics - const latestPrice = data.data[data.data.length - 1].close; - const earliestPrice = data.data[0].open; - const priceChange = ((latestPrice - earliestPrice) / earliestPrice) * 100; - const totalVolume = data.data.reduce((sum, d) => sum + d.volume, 0); - const highestPrice = Math.max(...data.data.map((d) => d.high)); - const lowestPrice = Math.min(...data.data.map((d) => d.low)); - const averageVolume = totalVolume / data.data.length; - const volatility = ((highestPrice - lowestPrice) / lowestPrice) * 100; - - response += `šŸ“Š Summary\n`; - response += `ā€¢ Current Price: ${formatValue(latestPrice)}\n`; - response += `ā€¢ Period Change: ${priceChange >= 0 ? "+" : ""}${priceChange.toFixed(2)}%\n`; - response += `ā€¢ Total Volume: ${formatValue(totalVolume)}\n`; - response += `ā€¢ Average Volume: ${formatValue(averageVolume)}\n`; - response += `ā€¢ Highest Price: ${formatValue(highestPrice)}\n`; - response += `ā€¢ Lowest Price: ${formatValue(lowestPrice)}\n`; - response += `ā€¢ Volatility: ${volatility.toFixed(2)}%\n\n`; - - // Add trend analysis - const trendStrength = Math.abs(priceChange); - let trendAnalysis = ""; - if (trendStrength < 1) { - trendAnalysis = "Sideways movement with low volatility"; - } else if (trendStrength < 5) { - trendAnalysis = - priceChange > 0 ? "Slight upward trend" : "Slight downward trend"; - } else if (trendStrength < 10) { - trendAnalysis = - priceChange > 0 - ? "Moderate upward trend" - : "Moderate downward trend"; - } else { - trendAnalysis = - priceChange > 0 ? "Strong upward trend" : "Strong downward trend"; - } - - response += `šŸ“ˆ Trend Analysis\n`; - response += `ā€¢ ${trendAnalysis}\n`; - response += `ā€¢ Volatility is ${volatility < 5 ? "low" : volatility < 15 ? "moderate" : "high"}\n\n`; - - response += `šŸ“Š Recent Data\n`; - // Show only the last 5 entries - const recentData = data.data.slice(-5); - recentData.forEach((candle, index) => { - response += `${index + 1}. ${formatOHLCVData(candle)}\n\n`; - }); - - if (data.data.length > 5) { - response += `Showing last 5 of ${data.data.length} candles.`; - } - - return response; -}; - -export const baseQuoteOHLCVProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsBaseQuoteOHLCVKeyword(messageText)) { - return null; - } - - const addresses = extractContractAddresses(messageText); - if (addresses.length !== 2) { - return null; - } - - const chain = extractChain(messageText); - const timeframe = extractTimeframe(messageText); - const timeRange = extractTimeRange(messageText); - const limit = extractLimit(messageText); - - elizaLogger.info( - `BASE/QUOTE OHLCV provider activated for base ${addresses[0]} and quote ${addresses[1]} with ${timeframe} timeframe on ${chain}` - ); - - const ohlcvData = await getBaseQuoteOHLCV( - apiKey, - addresses[0], - addresses[1], - timeframe, - chain, - limit - ); - - if (!ohlcvData) { - return null; - } - - return formatBaseQuoteOHLCVResponse( - ohlcvData, - timeframe, - chain, - timeRange - ); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/defi/ohlcv-pair-provider.ts b/packages/plugin-birdeye/src/providers/defi/ohlcv-pair-provider.ts deleted file mode 100644 index 8cfa6f9fac..0000000000 --- a/packages/plugin-birdeye/src/providers/defi/ohlcv-pair-provider.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { - BASE_URL, - Chain, - Timeframe, - extractChain, - extractContractAddresses, - extractLimit, - extractTimeRange, - extractTimeframe, - formatTimestamp, - formatValue, - makeApiRequest, -} from "../utils"; - -// Types -interface OHLCVData { - timestamp: number; - open: number; - high: number; - low: number; - close: number; - volume: number; -} - -interface PairOHLCVResponse { - data: OHLCVData[]; - pair: { - baseToken: string; - quoteToken: string; - }; -} - -// Constants -const PAIR_OHLCV_KEYWORDS = [ - "pair ohlcv", - "pair candlestick", - "pair candles", - "pair chart", - "pair price history", - "pair historical data", - "pair market data", - "pair trading data", - "trading chart", - "price chart", - "market chart", - "candlestick chart", - "price action", - "market action", - "price movement", - "market movement", -] as const; - -// Helper functions -const containsPairOHLCVKeyword = (text: string): boolean => { - return PAIR_OHLCV_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getPairOHLCV = async ( - apiKey: string, - baseAddress: string, - quoteAddress: string, - timeframe: Timeframe, - chain: Chain, - limit: number -): Promise => { - try { - const params = new URLSearchParams({ - base_address: baseAddress, - quote_address: quoteAddress, - timeframe, - limit: limit.toString(), - }); - const url = `${BASE_URL}/defi/ohlcv_pair?${params.toString()}`; - - elizaLogger.info( - `Fetching OHLCV data for pair ${baseAddress}/${quoteAddress} with ${timeframe} timeframe on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { apiKey, chain }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching pair OHLCV data:", error.message); - } - return null; - } -}; - -const formatOHLCVData = (data: OHLCVData): string => { - const timestamp = formatTimestamp(data.timestamp); - const change = ((data.close - data.open) / data.open) * 100; - const trend = change >= 0 ? "šŸŸ¢" : "šŸ”“"; - - let response = `${trend} ${timestamp}\n`; - response += `ā€¢ Open: ${formatValue(data.open)}\n`; - response += `ā€¢ High: ${formatValue(data.high)}\n`; - response += `ā€¢ Low: ${formatValue(data.low)}\n`; - response += `ā€¢ Close: ${formatValue(data.close)}\n`; - response += `ā€¢ Volume: ${formatValue(data.volume)}\n`; - response += `ā€¢ Change: ${change >= 0 ? "+" : ""}${change.toFixed(2)}%`; - - return response; -}; - -const formatPairOHLCVResponse = ( - data: PairOHLCVResponse, - timeframe: Timeframe, - chain: Chain, - timeRange: { start: number; end: number } -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - const startDate = formatTimestamp(timeRange.start); - const endDate = formatTimestamp(timeRange.end); - - let response = `OHLCV Data for ${data.pair.baseToken}/${data.pair.quoteToken} pair on ${chainName}\n`; - response += `Timeframe: ${timeframe} (${startDate} to ${endDate})\n\n`; - - if (data.data.length === 0) { - return response + "No OHLCV data found for this pair."; - } - - // Calculate summary statistics - const latestPrice = data.data[data.data.length - 1].close; - const earliestPrice = data.data[0].open; - const priceChange = ((latestPrice - earliestPrice) / earliestPrice) * 100; - const totalVolume = data.data.reduce((sum, d) => sum + d.volume, 0); - const highestPrice = Math.max(...data.data.map((d) => d.high)); - const lowestPrice = Math.min(...data.data.map((d) => d.low)); - const averageVolume = totalVolume / data.data.length; - - response += `šŸ“Š Summary\n`; - response += `ā€¢ Current Price: ${formatValue(latestPrice)}\n`; - response += `ā€¢ Period Change: ${priceChange >= 0 ? "+" : ""}${priceChange.toFixed(2)}%\n`; - response += `ā€¢ Total Volume: ${formatValue(totalVolume)}\n`; - response += `ā€¢ Average Volume: ${formatValue(averageVolume)}\n`; - response += `ā€¢ Highest Price: ${formatValue(highestPrice)}\n`; - response += `ā€¢ Lowest Price: ${formatValue(lowestPrice)}\n`; - response += `ā€¢ Price Range: ${(((highestPrice - lowestPrice) / lowestPrice) * 100).toFixed(2)}%\n\n`; - - response += `šŸ“ˆ Recent Data\n`; - // Show only the last 5 entries - const recentData = data.data.slice(-5); - recentData.forEach((candle, index) => { - response += `${index + 1}. ${formatOHLCVData(candle)}\n\n`; - }); - - if (data.data.length > 5) { - response += `Showing last 5 of ${data.data.length} candles.`; - } - - return response; -}; - -export const pairOHLCVProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsPairOHLCVKeyword(messageText)) { - return null; - } - - const addresses = extractContractAddresses(messageText); - if (addresses.length !== 2) { - return null; - } - - const chain = extractChain(messageText); - const timeframe = extractTimeframe(messageText); - const timeRange = extractTimeRange(messageText); - const limit = extractLimit(messageText); - - elizaLogger.info( - `PAIR OHLCV provider activated for base ${addresses[0]} and quote ${addresses[1]} with ${timeframe} timeframe on ${chain}` - ); - - const ohlcvData = await getPairOHLCV( - apiKey, - addresses[0], - addresses[1], - timeframe, - chain, - limit - ); - - if (!ohlcvData) { - return null; - } - - return formatPairOHLCVResponse(ohlcvData, timeframe, chain, timeRange); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/defi/ohlcv-provider.ts b/packages/plugin-birdeye/src/providers/defi/ohlcv-provider.ts deleted file mode 100644 index 259e1b4a0d..0000000000 --- a/packages/plugin-birdeye/src/providers/defi/ohlcv-provider.ts +++ /dev/null @@ -1,256 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; - -// Types -interface OHLCVData { - timestamp: number; - open: number; - high: number; - low: number; - close: number; - volume: number; -} - -// Constants -const OHLCV_KEYWORDS = [ - "ohlc", - "ohlcv", - "candlestick", - "candle", - "chart", - "price history", - "historical", -] as const; - -const TIME_INTERVAL_KEYWORDS = { - "1m": ["1 minute", "1min", "1m"], - "3m": ["3 minutes", "3min", "3m"], - "5m": ["5 minutes", "5min", "5m"], - "15m": ["15 minutes", "15min", "15m"], - "30m": ["30 minutes", "30min", "30m"], - "1h": ["1 hour", "1hr", "1h"], - "2h": ["2 hours", "2hr", "2h"], - "4h": ["4 hours", "4hr", "4h"], - "6h": ["6 hours", "6hr", "6h"], - "12h": ["12 hours", "12hr", "12h"], - "1d": ["1 day", "daily", "1d"], - "1w": ["1 week", "weekly", "1w"], - "1mo": ["1 month", "monthly", "1mo"], -} as const; - -const CHAIN_KEYWORDS = [ - "solana", - "ethereum", - "arbitrum", - "avalanche", - "bsc", - "optimism", - "polygon", - "base", - "zksync", - "sui", -] as const; - -const BASE_URL = "https://public-api.birdeye.so"; - -// Helper functions -const containsOHLCVKeyword = (text: string): boolean => { - return OHLCV_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const extractTimeInterval = (text: string): string => { - const lowerText = text.toLowerCase(); - for (const [interval, keywords] of Object.entries(TIME_INTERVAL_KEYWORDS)) { - if (keywords.some((keyword) => lowerText.includes(keyword))) { - return interval; - } - } - return "1d"; // Default to daily if no interval specified -}; - -const extractChain = (text: string): string => { - const chain = CHAIN_KEYWORDS.find((chain) => - text.toLowerCase().includes(chain.toLowerCase()) - ); - return chain || "solana"; -}; - -const extractContractAddress = (text: string): string | null => { - const words = text.split(/\s+/); - - for (const word of words) { - // Ethereum-like addresses (0x...) - if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { - return word; - } - // Solana addresses (base58, typically 32-44 chars) - if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { - return word; - } - } - return null; -}; - -const getOHLCVData = async ( - apiKey: string, - contractAddress: string, - interval: string = "1d", - chain: string = "solana" -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - interval, - limit: "24", // Get last 24 periods - }); - const url = `${BASE_URL}/defi/ohlcv?${params.toString()}`; - - elizaLogger.info( - `Fetching OHLCV data for address ${contractAddress} on ${chain} with interval ${interval} from:`, - url - ); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - "x-chain": chain, - }, - }); - - if (!response.ok) { - if (response.status === 404) { - elizaLogger.warn( - `Token not found: ${contractAddress} on ${chain}` - ); - return null; - } - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - return data.data; - } catch (error) { - elizaLogger.error("Error fetching OHLCV data:", error); - return null; - } -}; - -const formatNumber = (num: number): string => { - if (!num && num !== 0) return "N/A"; - return num < 0.01 - ? num.toExponential(2) - : num.toLocaleString("en-US", { - minimumFractionDigits: 2, - maximumFractionDigits: 6, - }); -}; - -const formatVolume = (volume: number): string => { - if (volume >= 1_000_000_000) { - return `$${(volume / 1_000_000_000).toFixed(2)}B`; - } - if (volume >= 1_000_000) { - return `$${(volume / 1_000_000).toFixed(2)}M`; - } - if (volume >= 1_000) { - return `$${(volume / 1_000).toFixed(2)}K`; - } - return `$${volume.toFixed(2)}`; -}; - -const formatOHLCVResponse = ( - data: OHLCVData[], - interval: string, - chain: string -): string => { - if (data.length === 0) { - return "No OHLCV data available for the specified period."; - } - - // Sort data by timestamp in ascending order - const sortedData = [...data].sort((a, b) => a.timestamp - b.timestamp); - const latestData = sortedData[sortedData.length - 1]; - - let response = `OHLCV Data (${interval}) on ${chain.charAt(0).toUpperCase() + chain.slice(1)}:\n\n`; - - // Latest price information - response += `šŸ“Š Latest Candle (${new Date(latestData.timestamp * 1000).toLocaleString()})\n`; - response += `ā€¢ Open: $${formatNumber(latestData.open)}\n`; - response += `ā€¢ High: $${formatNumber(latestData.high)}\n`; - response += `ā€¢ Low: $${formatNumber(latestData.low)}\n`; - response += `ā€¢ Close: $${formatNumber(latestData.close)}\n`; - response += `ā€¢ Volume: ${formatVolume(latestData.volume)}\n`; - - // Price change statistics - const priceChange = latestData.close - latestData.open; - const priceChangePercent = (priceChange / latestData.open) * 100; - const trend = priceChange >= 0 ? "šŸ“ˆ" : "šŸ“‰"; - - response += `\n${trend} Period Change\n`; - response += `ā€¢ Price Change: $${formatNumber(priceChange)} (${priceChangePercent.toFixed(2)}%)\n`; - - // Volume analysis - const totalVolume = sortedData.reduce( - (sum, candle) => sum + candle.volume, - 0 - ); - const avgVolume = totalVolume / sortedData.length; - - response += `\nšŸ“Š Volume Analysis\n`; - response += `ā€¢ Total Volume: ${formatVolume(totalVolume)}\n`; - response += `ā€¢ Average Volume: ${formatVolume(avgVolume)}\n`; - - return response; -}; - -export const ohlcvProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsOHLCVKeyword(messageText)) { - return null; - } - - const contractAddress = extractContractAddress(messageText); - if (!contractAddress) { - return null; - } - - const chain = extractChain(messageText); - const interval = extractTimeInterval(messageText); - - elizaLogger.info( - `OHLCV provider activated for address ${contractAddress} on ${chain} with interval ${interval}` - ); - - const ohlcvData = await getOHLCVData( - apiKey, - contractAddress, - interval, - chain - ); - - if (!ohlcvData) { - return null; - } - - return formatOHLCVResponse(ohlcvData, interval, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/defi/pair-trades-provider.ts b/packages/plugin-birdeye/src/providers/defi/pair-trades-provider.ts deleted file mode 100644 index d0eefb6da9..0000000000 --- a/packages/plugin-birdeye/src/providers/defi/pair-trades-provider.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { - BASE_URL, - Chain, - extractChain, - extractContractAddresses, - extractLimit, - formatTimestamp, - formatValue, - makeApiRequest, - shortenAddress, -} from "../utils"; - -// Types -interface PairTrade { - timestamp: number; - price: number; - volume: number; - side: "buy" | "sell"; - source: string; - txHash: string; - buyer?: string; - seller?: string; - baseToken: string; - quoteToken: string; -} - -interface PairTradesResponse { - trades: PairTrade[]; - totalCount: number; - pair: { - baseToken: string; - quoteToken: string; - }; -} - -// Constants -const PAIR_TRADES_KEYWORDS = [ - "pair trades", - "pair swaps", - "pair transactions", - "pair activity", - "pair orders", - "pair executions", - "pair trading", - "pair market activity", - "pair exchange activity", - "pair trading history", - "pair market history", - "pair exchange history", - "trading pair activity", - "trading pair history", - "base/quote trades", - "base/quote activity", -] as const; - -// Helper functions -const containsPairTradesKeyword = (text: string): boolean => { - return PAIR_TRADES_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getPairTrades = async ( - apiKey: string, - baseAddress: string, - quoteAddress: string, - chain: Chain, - limit: number -): Promise => { - try { - const params = new URLSearchParams({ - base_address: baseAddress, - quote_address: quoteAddress, - limit: limit.toString(), - }); - const url = `${BASE_URL}/defi/trades_pair?${params.toString()}`; - - elizaLogger.info( - `Fetching pair trades for base ${baseAddress} and quote ${quoteAddress} on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { apiKey, chain }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching pair trades:", error.message); - } - return null; - } -}; - -const formatPairTrade = (trade: PairTrade): string => { - const timestamp = formatTimestamp(trade.timestamp); - const side = trade.side === "buy" ? "šŸŸ¢ Buy" : "šŸ”“ Sell"; - - let response = `${side} - ${timestamp}\n`; - response += `ā€¢ Price: ${formatValue(trade.price)}\n`; - response += `ā€¢ Volume: ${formatValue(trade.volume)}\n`; - response += `ā€¢ Source: ${trade.source}\n`; - if (trade.buyer && trade.seller) { - response += `ā€¢ Buyer: ${shortenAddress(trade.buyer)}\n`; - response += `ā€¢ Seller: ${shortenAddress(trade.seller)}\n`; - } - response += `ā€¢ Tx: ${shortenAddress(trade.txHash)}`; - - return response; -}; - -const formatPairTradesResponse = ( - data: PairTradesResponse, - chain: Chain -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - - let response = `Recent Trades for ${data.pair.baseToken}/${data.pair.quoteToken} pair on ${chainName}\n\n`; - - if (data.trades.length === 0) { - return response + "No trades found."; - } - - // Calculate summary statistics - const totalVolume = data.trades.reduce((sum, t) => sum + t.volume, 0); - const averageVolume = totalVolume / data.trades.length; - const buyCount = data.trades.filter((t) => t.side === "buy").length; - const buyRatio = (buyCount / data.trades.length) * 100; - const averagePrice = - data.trades.reduce((sum, t) => sum + t.price, 0) / data.trades.length; - const priceChange = - ((data.trades[data.trades.length - 1].price - data.trades[0].price) / - data.trades[0].price) * - 100; - const highestPrice = Math.max(...data.trades.map((t) => t.price)); - const lowestPrice = Math.min(...data.trades.map((t) => t.price)); - const priceRange = ((highestPrice - lowestPrice) / lowestPrice) * 100; - - response += `šŸ“Š Summary\n`; - response += `ā€¢ Total Trades: ${data.trades.length}\n`; - response += `ā€¢ Total Volume: ${formatValue(totalVolume)}\n`; - response += `ā€¢ Average Volume: ${formatValue(averageVolume)}\n`; - response += `ā€¢ Buy/Sell Ratio: ${buyRatio.toFixed(1)}% buys\n`; - response += `ā€¢ Average Price: ${formatValue(averagePrice)}\n`; - response += `ā€¢ Price Change: ${priceChange >= 0 ? "+" : ""}${priceChange.toFixed(2)}%\n`; - response += `ā€¢ Price Range: ${priceRange.toFixed(2)}%\n\n`; - - // Add market analysis - const volatility = priceRange / Math.sqrt(data.trades.length); - const volumePerTrade = totalVolume / data.trades.length; - let marketAnalysis = ""; - - if (data.trades.length < 5) { - marketAnalysis = "Insufficient data for detailed analysis"; - } else { - // Analyze trading activity - const activityLevel = - data.trades.length > 20 - ? "high" - : data.trades.length > 10 - ? "moderate" - : "low"; - const volumeLevel = - volumePerTrade > averageVolume * 2 - ? "high" - : volumePerTrade > averageVolume - ? "moderate" - : "low"; - const volatilityLevel = - volatility > 5 ? "high" : volatility > 2 ? "moderate" : "low"; - const trend = - Math.abs(priceChange) < 1 - ? "sideways" - : priceChange > 0 - ? "upward" - : "downward"; - - marketAnalysis = `Market shows ${activityLevel} trading activity with ${volumeLevel} volume per trade. `; - marketAnalysis += `${volatilityLevel.charAt(0).toUpperCase() + volatilityLevel.slice(1)} volatility with a ${trend} price trend.`; - } - - response += `šŸ“ˆ Market Analysis\n`; - response += `ā€¢ ${marketAnalysis}\n\n`; - - response += `šŸ”„ Recent Trades\n`; - data.trades.forEach((trade, index) => { - response += `${index + 1}. ${formatPairTrade(trade)}\n\n`; - }); - - if (data.totalCount > data.trades.length) { - response += `Showing ${data.trades.length} of ${data.totalCount} total trades.`; - } - - return response; -}; - -export const pairTradesProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsPairTradesKeyword(messageText)) { - return null; - } - - const addresses = extractContractAddresses(messageText); - if (addresses.length !== 2) { - return null; - } - - const chain = extractChain(messageText); - const limit = extractLimit(messageText); - - elizaLogger.info( - `PAIR TRADES provider activated for base ${addresses[0]} and quote ${addresses[1]} on ${chain}` - ); - - const tradesData = await getPairTrades( - apiKey, - addresses[0], - addresses[1], - chain, - limit - ); - - if (!tradesData) { - return null; - } - - return formatPairTradesResponse(tradesData, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/defi/pair-trades-seek-provider.ts b/packages/plugin-birdeye/src/providers/defi/pair-trades-seek-provider.ts deleted file mode 100644 index 7d293d1d68..0000000000 --- a/packages/plugin-birdeye/src/providers/defi/pair-trades-seek-provider.ts +++ /dev/null @@ -1,266 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { - BASE_URL, - Chain, - extractChain, - extractContractAddresses, - extractLimit, - extractTimeRange, - formatTimestamp, - formatValue, - makeApiRequest, - shortenAddress, -} from "../utils"; - -// Types -interface PairTrade { - timestamp: number; - price: number; - volume: number; - side: "buy" | "sell"; - source: string; - txHash: string; - buyer?: string; - seller?: string; - baseToken: string; - quoteToken: string; -} - -interface PairTradesResponse { - trades: PairTrade[]; - totalCount: number; - pair: { - baseToken: string; - quoteToken: string; - }; -} - -// Constants -const PAIR_TRADE_KEYWORDS = [ - "pair trades", - "pair trading", - "pair transactions", - "pair swaps", - "pair buys", - "pair sells", - "pair orders", - "pair executions", - "pair trade history", - "pair trading history", - "pair recent trades", - "pair market activity", - "pair trading activity", - "pair market trades", - "pair exchange history", - "trading pair history", - "trading pair activity", - "base/quote trades", - "base/quote activity", -] as const; - -// Helper functions -const containsPairTradeKeyword = (text: string): boolean => { - return PAIR_TRADE_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getPairTradesByTime = async ( - apiKey: string, - baseAddress: string, - quoteAddress: string, - timestamp: number, - chain: Chain, - limit: number -): Promise => { - try { - const params = new URLSearchParams({ - base_address: baseAddress, - quote_address: quoteAddress, - timestamp: timestamp.toString(), - limit: limit.toString(), - }); - const url = `${BASE_URL}/defi/trades_pair_seek_time?${params.toString()}`; - - elizaLogger.info( - `Fetching pair trades for base ${baseAddress} and quote ${quoteAddress} since ${new Date( - timestamp * 1000 - ).toLocaleString()} on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { apiKey, chain }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error( - "Error fetching pair trades by time:", - error.message - ); - } - return null; - } -}; - -const formatPairTrade = (trade: PairTrade): string => { - const timestamp = formatTimestamp(trade.timestamp); - const side = trade.side === "buy" ? "šŸŸ¢ Buy" : "šŸ”“ Sell"; - - let response = `${side} - ${timestamp}\n`; - response += `ā€¢ Price: ${formatValue(trade.price)}\n`; - response += `ā€¢ Volume: ${formatValue(trade.volume)}\n`; - response += `ā€¢ Source: ${trade.source}\n`; - if (trade.buyer && trade.seller) { - response += `ā€¢ Buyer: ${shortenAddress(trade.buyer)}\n`; - response += `ā€¢ Seller: ${shortenAddress(trade.seller)}\n`; - } - response += `ā€¢ Tx: ${shortenAddress(trade.txHash)}`; - - return response; -}; - -const formatPairTradesResponse = ( - data: PairTradesResponse, - timeRange: { start: number; end: number }, - chain: Chain -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - const startDate = formatTimestamp(timeRange.start); - const endDate = formatTimestamp(timeRange.end); - - let response = `Trade History for ${data.pair.baseToken}/${data.pair.quoteToken} pair on ${chainName}\n`; - response += `Period: ${startDate} to ${endDate}\n\n`; - - if (data.trades.length === 0) { - return response + "No trades found in this time period."; - } - - // Calculate summary statistics - const totalVolume = data.trades.reduce((sum, t) => sum + t.volume, 0); - const averageVolume = totalVolume / data.trades.length; - const buyCount = data.trades.filter((t) => t.side === "buy").length; - const buyRatio = (buyCount / data.trades.length) * 100; - const averagePrice = - data.trades.reduce((sum, t) => sum + t.price, 0) / data.trades.length; - const priceChange = - ((data.trades[data.trades.length - 1].price - data.trades[0].price) / - data.trades[0].price) * - 100; - const highestPrice = Math.max(...data.trades.map((t) => t.price)); - const lowestPrice = Math.min(...data.trades.map((t) => t.price)); - const priceRange = ((highestPrice - lowestPrice) / lowestPrice) * 100; - - response += `šŸ“Š Summary\n`; - response += `ā€¢ Total Trades: ${data.trades.length}\n`; - response += `ā€¢ Total Volume: ${formatValue(totalVolume)}\n`; - response += `ā€¢ Average Volume: ${formatValue(averageVolume)}\n`; - response += `ā€¢ Buy/Sell Ratio: ${buyRatio.toFixed(1)}% buys\n`; - response += `ā€¢ Average Price: ${formatValue(averagePrice)}\n`; - response += `ā€¢ Price Change: ${priceChange >= 0 ? "+" : ""}${priceChange.toFixed(2)}%\n`; - response += `ā€¢ Price Range: ${priceRange.toFixed(2)}%\n\n`; - - // Add market analysis - const volatility = priceRange / Math.sqrt(data.trades.length); - const volumePerTrade = totalVolume / data.trades.length; - let marketAnalysis = ""; - - if (data.trades.length < 5) { - marketAnalysis = "Insufficient data for detailed analysis"; - } else { - // Analyze trading activity - const activityLevel = - data.trades.length > 20 - ? "high" - : data.trades.length > 10 - ? "moderate" - : "low"; - const volumeLevel = - volumePerTrade > averageVolume * 2 - ? "high" - : volumePerTrade > averageVolume - ? "moderate" - : "low"; - const volatilityLevel = - volatility > 5 ? "high" : volatility > 2 ? "moderate" : "low"; - const trend = - Math.abs(priceChange) < 1 - ? "sideways" - : priceChange > 0 - ? "upward" - : "downward"; - - marketAnalysis = `Market shows ${activityLevel} trading activity with ${volumeLevel} volume per trade. `; - marketAnalysis += `${volatilityLevel.charAt(0).toUpperCase() + volatilityLevel.slice(1)} volatility with a ${trend} price trend.`; - } - - response += `šŸ“ˆ Market Analysis\n`; - response += `ā€¢ ${marketAnalysis}\n\n`; - - response += `šŸ”„ Recent Trades\n`; - data.trades.forEach((trade, index) => { - response += `${index + 1}. ${formatPairTrade(trade)}\n\n`; - }); - - if (data.totalCount > data.trades.length) { - response += `Showing ${data.trades.length} of ${data.totalCount} total trades.`; - } - - return response; -}; - -export const pairTradesSeekProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsPairTradeKeyword(messageText)) { - return null; - } - - const addresses = extractContractAddresses(messageText); - if (addresses.length !== 2) { - return null; - } - - const chain = extractChain(messageText); - const timeRange = extractTimeRange(messageText); - const limit = extractLimit(messageText); - - elizaLogger.info( - `PAIR TRADES SEEK provider activated for base ${addresses[0]} and quote ${addresses[1]} from ${new Date( - timeRange.start * 1000 - ).toLocaleString()} to ${new Date( - timeRange.end * 1000 - ).toLocaleString()} on ${chain}` - ); - - const tradesData = await getPairTradesByTime( - apiKey, - addresses[0], - addresses[1], - timeRange.start, - chain, - limit - ); - - if (!tradesData) { - return null; - } - - return formatPairTradesResponse(tradesData, timeRange, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/defi/price-history-provider.ts b/packages/plugin-birdeye/src/providers/defi/price-history-provider.ts deleted file mode 100644 index 69c2275488..0000000000 --- a/packages/plugin-birdeye/src/providers/defi/price-history-provider.ts +++ /dev/null @@ -1,230 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { - BASE_URL, - Chain, - extractChain, - extractContractAddresses, - extractTimeRange, - formatTimestamp, - formatValue, - makeApiRequest, -} from "../utils"; - -// Types -interface PriceHistoryData { - price: number; - timestamp: number; - volume?: number; -} - -interface PriceHistoryResponse { - data: PriceHistoryData[]; - token: string; -} - -// Constants -const PRICE_HISTORY_KEYWORDS = [ - "price history", - "historical price", - "price chart", - "price trend", - "price movement", - "price changes", - "price over time", - "price timeline", - "price performance", - "price data", - "historical data", - "price analysis", - "price tracking", - "price evolution", -] as const; - -// Helper functions -const containsPriceHistoryKeyword = (text: string): boolean => { - return PRICE_HISTORY_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getPriceHistory = async ( - apiKey: string, - contractAddress: string, - startTime: number, - endTime: number, - chain: Chain -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - time_from: startTime.toString(), - time_to: endTime.toString(), - }); - const url = `${BASE_URL}/defi/price_history_unix?${params.toString()}`; - - elizaLogger.info( - `Fetching price history for token ${contractAddress} from ${new Date( - startTime * 1000 - ).toLocaleString()} to ${new Date( - endTime * 1000 - ).toLocaleString()} on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { - apiKey, - chain, - }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching price history:", error.message); - } - return null; - } -}; - -const formatPriceHistoryResponse = ( - data: PriceHistoryResponse, - timeRange: { start: number; end: number }, - chain: Chain -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - const startDate = formatTimestamp(timeRange.start); - const endDate = formatTimestamp(timeRange.end); - - let response = `Price History for ${data.token} on ${chainName}\n`; - response += `Period: ${startDate} to ${endDate}\n\n`; - - if (data.data.length === 0) { - return response + "No price data found for this period."; - } - - // Calculate summary statistics - const prices = data.data.map((d) => d.price); - const volumes = data.data.map((d) => d.volume || 0); - const startPrice = data.data[0].price; - const endPrice = data.data[data.data.length - 1].price; - const priceChange = ((endPrice - startPrice) / startPrice) * 100; - const highestPrice = Math.max(...prices); - const lowestPrice = Math.min(...prices); - const averagePrice = prices.reduce((a, b) => a + b, 0) / prices.length; - const totalVolume = volumes.reduce((a, b) => a + b, 0); - const volatility = ((highestPrice - lowestPrice) / averagePrice) * 100; - - response += `šŸ“Š Summary\n`; - response += `ā€¢ Start Price: ${formatValue(startPrice)}\n`; - response += `ā€¢ End Price: ${formatValue(endPrice)}\n`; - response += `ā€¢ Price Change: ${priceChange >= 0 ? "+" : ""}${priceChange.toFixed(2)}%\n`; - response += `ā€¢ Highest Price: ${formatValue(highestPrice)}\n`; - response += `ā€¢ Lowest Price: ${formatValue(lowestPrice)}\n`; - response += `ā€¢ Average Price: ${formatValue(averagePrice)}\n`; - if (totalVolume > 0) { - response += `ā€¢ Total Volume: ${formatValue(totalVolume)}\n`; - } - response += `ā€¢ Volatility: ${volatility.toFixed(2)}%\n\n`; - - // Add trend analysis - const trendStrength = Math.abs(priceChange); - let trendAnalysis = ""; - if (trendStrength < 1) { - trendAnalysis = "Price has remained relatively stable"; - } else if (trendStrength < 5) { - trendAnalysis = - priceChange > 0 - ? "Price shows slight upward movement" - : "Price shows slight downward movement"; - } else if (trendStrength < 10) { - trendAnalysis = - priceChange > 0 - ? "Price demonstrates moderate upward trend" - : "Price demonstrates moderate downward trend"; - } else { - trendAnalysis = - priceChange > 0 - ? "Price exhibits strong upward momentum" - : "Price exhibits strong downward momentum"; - } - - response += `šŸ“ˆ Trend Analysis\n`; - response += `ā€¢ ${trendAnalysis}\n`; - response += `ā€¢ Volatility is ${volatility < 10 ? "low" : volatility < 25 ? "moderate" : "high"}\n\n`; - - // Show key price points - response += `šŸ”‘ Key Price Points\n`; - const keyPoints = [ - { label: "Start", ...data.data[0] }, - { - label: "High", - price: highestPrice, - timestamp: data.data[prices.indexOf(highestPrice)].timestamp, - }, - { - label: "Low", - price: lowestPrice, - timestamp: data.data[prices.indexOf(lowestPrice)].timestamp, - }, - { label: "End", ...data.data[data.data.length - 1] }, - ]; - - keyPoints.forEach((point) => { - response += `ā€¢ ${point.label}: ${formatValue(point.price)} (${formatTimestamp(point.timestamp)})\n`; - }); - - return response; -}; - -export const priceHistoryProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsPriceHistoryKeyword(messageText)) { - return null; - } - - const addresses = extractContractAddresses(messageText); - if (addresses.length === 0) { - return null; - } - - const chain = extractChain(messageText); - const timeRange = extractTimeRange(messageText); - - elizaLogger.info( - `PRICE HISTORY provider activated for token ${addresses[0]} from ${new Date( - timeRange.start * 1000 - ).toLocaleString()} to ${new Date( - timeRange.end * 1000 - ).toLocaleString()} on ${chain}` - ); - - const priceData = await getPriceHistory( - apiKey, - addresses[0], - timeRange.start, - timeRange.end, - chain - ); - - if (!priceData) { - return null; - } - - return formatPriceHistoryResponse(priceData, timeRange, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/defi/price-multiple-provider.ts b/packages/plugin-birdeye/src/providers/defi/price-multiple-provider.ts deleted file mode 100644 index ad4c95bbde..0000000000 --- a/packages/plugin-birdeye/src/providers/defi/price-multiple-provider.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; - -// Types -interface TokenPrice { - price: number; - timestamp: number; - token: string; - priceChange24h?: number; - priceChange24hPercent?: number; -} - -interface MultiPriceResponse { - [tokenAddress: string]: TokenPrice; -} - -// Constants -const PRICE_KEYWORDS = [ - "price", - "prices", - "cost", - "worth", - "value", - "compare", - "multiple", - "several", - "many", - "list of", - "these tokens", - "their prices", -] as const; - -const CHAIN_KEYWORDS = [ - "solana", - "ethereum", - "arbitrum", - "avalanche", - "bsc", - "optimism", - "polygon", - "base", - "zksync", - "sui", -] as const; - -const BASE_URL = "https://public-api.birdeye.so"; - -// Helper functions -const containsPriceKeyword = (text: string): boolean => { - return PRICE_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const extractChain = (text: string): string => { - const chain = CHAIN_KEYWORDS.find((chain) => - text.toLowerCase().includes(chain.toLowerCase()) - ); - return chain || "solana"; -}; - -const extractContractAddresses = (text: string): string[] => { - const words = text.split(/\s+/); - const addresses: string[] = []; - - for (const word of words) { - // Ethereum-like addresses (0x...) - if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { - addresses.push(word); - } - // Solana addresses (base58, typically 32-44 chars) - if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { - addresses.push(word); - } - } - return addresses; -}; - -const getMultiplePrices = async ( - apiKey: string, - addresses: string[], - chain: string = "solana" -): Promise => { - try { - const params = new URLSearchParams({ - tokens: addresses.join(","), - }); - const url = `${BASE_URL}/defi/price_multiple?${params.toString()}`; - - elizaLogger.info( - `Fetching prices for ${addresses.length} tokens on ${chain} from:`, - url - ); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - "x-chain": chain, - }, - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - return data.data; - } catch (error) { - elizaLogger.error("Error fetching multiple prices:", error); - return null; - } -}; - -const formatNumber = (num: number): string => { - if (!num && num !== 0) return "N/A"; - return num < 0.01 - ? num.toExponential(2) - : num.toLocaleString("en-US", { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }); -}; - -const formatPriceResponse = ( - prices: MultiPriceResponse, - chain: string -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - let response = `Token Prices on ${chainName}:\n\n`; - - const sortedTokens = Object.entries(prices).sort((a, b) => { - const priceA = a[1].price || 0; - const priceB = b[1].price || 0; - return priceB - priceA; - }); - - sortedTokens.forEach(([address, data]) => { - const timestamp = new Date(data.timestamp * 1000).toLocaleString(); - response += `${data.token} (${address.slice(0, 6)}...${address.slice(-4)}):\n`; - response += `ā€¢ Price: $${formatNumber(data.price)}\n`; - - if (data.priceChange24h !== undefined) { - const changeSymbol = data.priceChange24h >= 0 ? "šŸ“ˆ" : "šŸ“‰"; - response += `ā€¢ 24h Change: ${changeSymbol} $${formatNumber(Math.abs(data.priceChange24h))} `; - if (data.priceChange24hPercent !== undefined) { - response += `(${data.priceChange24hPercent.toFixed(2)}%)`; - } - response += "\n"; - } - - response += `ā€¢ Last Updated: ${timestamp}\n\n`; - }); - - return response; -}; - -export const priceMultipleProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsPriceKeyword(messageText)) { - return null; - } - - const addresses = extractContractAddresses(messageText); - if (addresses.length < 2) { - // If less than 2 addresses found, let the single price provider handle it - return null; - } - - const chain = extractChain(messageText); - - elizaLogger.info( - `MULTIPLE PRICE provider activated for ${addresses.length} addresses on ${chain}` - ); - - const priceData = await getMultiplePrices(apiKey, addresses, chain); - - if (!priceData) { - return null; - } - - return formatPriceResponse(priceData, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/defi/price-provider.ts b/packages/plugin-birdeye/src/providers/defi/price-provider.ts deleted file mode 100644 index f7c6cd45a8..0000000000 --- a/packages/plugin-birdeye/src/providers/defi/price-provider.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; - -// Types -interface PriceData { - price: number; - timestamp: number; - token: string; - priceChange24h?: number; - priceChange24hPercent?: number; -} - -// Constants -const PRICE_KEYWORDS = [ - "price", - "cost", - "worth", - "value", - "rate", - "quote", - "how much", -] as const; - -const CHAIN_KEYWORDS = [ - "solana", - "ethereum", - "arbitrum", - "avalanche", - "bsc", - "optimism", - "polygon", - "base", - "zksync", - "sui", -] as const; - -const BASE_URL = "https://public-api.birdeye.so"; - -// Helper functions -const containsPriceKeyword = (text: string): boolean => { - return PRICE_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const extractChain = (text: string): string => { - const chain = CHAIN_KEYWORDS.find((chain) => - text.toLowerCase().includes(chain.toLowerCase()) - ); - return chain || "solana"; -}; - -const extractContractAddress = (text: string): string | null => { - const words = text.split(/\s+/); - - for (const word of words) { - // Ethereum-like addresses (0x...) - if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { - return word; - } - // Solana addresses (base58, typically 32-44 chars) - if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { - return word; - } - } - return null; -}; - -const getTokenPrice = async ( - apiKey: string, - contractAddress: string, - chain: string = "solana" -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - }); - const url = `${BASE_URL}/defi/price?${params.toString()}`; - - elizaLogger.info( - `Fetching price for address ${contractAddress} on ${chain} from:`, - url - ); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - "x-chain": chain, - }, - }); - - if (!response.ok) { - if (response.status === 404) { - elizaLogger.warn( - `Token not found: ${contractAddress} on ${chain}` - ); - return null; - } - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - return data.data; - } catch (error) { - elizaLogger.error("Error fetching token price:", error); - return null; - } -}; - -const formatPriceResponse = (price: PriceData, chain: string): string => { - const timestamp = new Date(price.timestamp * 1000).toLocaleString(); - const priceFormatted = - price.price < 0.01 - ? price.price.toExponential(2) - : price.price.toFixed(2); - - let response = `Price for ${price.token} on ${chain.charAt(0).toUpperCase() + chain.slice(1)}:\n\n`; - response += `ā€¢ Current Price: $${priceFormatted}\n`; - - if (price.priceChange24h !== undefined) { - response += `ā€¢ 24h Change: $${price.priceChange24h.toFixed(2)}\n`; - } - - if (price.priceChange24hPercent !== undefined) { - response += `ā€¢ 24h Change %: ${price.priceChange24hPercent.toFixed(2)}%\n`; - } - - response += `ā€¢ Last Updated: ${timestamp}`; - - return response; -}; - -export const priceProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsPriceKeyword(messageText)) { - return null; - } - - const contractAddress = extractContractAddress(messageText); - if (!contractAddress) { - return null; - } - - const chain = extractChain(messageText); - - elizaLogger.info( - `PRICE provider activated for address ${contractAddress} on ${chain}` - ); - - const priceData = await getTokenPrice(apiKey, contractAddress, chain); - - if (!priceData) { - return null; - } - - return formatPriceResponse(priceData, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/defi/price-volume-provider.ts b/packages/plugin-birdeye/src/providers/defi/price-volume-provider.ts deleted file mode 100644 index 1448fbb47f..0000000000 --- a/packages/plugin-birdeye/src/providers/defi/price-volume-provider.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { - BASE_URL, - Chain, - extractChain, - extractContractAddresses, - formatPercentChange, - formatTimestamp, - formatValue, - makeApiRequest, -} from "../utils"; - -// Types -interface PriceVolumeData { - price: number; - volume24h: number; - timestamp: number; - token: string; - priceChange24h?: number; - priceChange24hPercent?: number; - volumeChange24h?: number; - volumeChange24hPercent?: number; -} - -interface MultiPriceVolumeResponse { - [tokenAddress: string]: PriceVolumeData; -} - -// Constants -const PRICE_VOLUME_KEYWORDS = [ - "price and volume", - "volume and price", - "trading volume", - "market activity", - "market data", - "trading data", - "market stats", - "trading stats", - "market metrics", - "trading metrics", -] as const; - -// Helper functions -const containsPriceVolumeKeyword = (text: string): boolean => { - return PRICE_VOLUME_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getSinglePriceVolume = async ( - apiKey: string, - contractAddress: string, - chain: Chain -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - }); - const url = `${BASE_URL}/defi/price_volume_single?${params.toString()}`; - - elizaLogger.info( - `Fetching price/volume data for address ${contractAddress} on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { apiKey, chain }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error( - "Error fetching price/volume data:", - error.message - ); - } - return null; - } -}; - -const getMultiplePriceVolume = async ( - apiKey: string, - addresses: string[], - chain: Chain -): Promise => { - try { - const url = `${BASE_URL}/defi/price_volume_multi`; - - elizaLogger.info( - `Fetching price/volume data for ${addresses.length} tokens on ${chain}` - ); - - return await makeApiRequest(url, { - apiKey, - chain, - method: "POST", - body: { addresses }, - }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error( - "Error fetching multiple price/volume data:", - error.message - ); - } - return null; - } -}; - -const formatSinglePriceVolumeResponse = ( - data: PriceVolumeData, - chain: Chain -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - const timestamp = formatTimestamp(data.timestamp); - const priceFormatted = formatValue(data.price); - - let response = `Price & Volume Data for ${data.token} on ${chainName}:\n\n`; - - response += `šŸ’° Price Metrics\n`; - response += `ā€¢ Current Price: ${priceFormatted}\n`; - if (data.priceChange24h !== undefined) { - response += `ā€¢ 24h Price Change: ${formatValue(data.priceChange24h)} (${formatPercentChange(data.priceChange24hPercent)})\n`; - } - - response += `\nšŸ“Š Volume Metrics\n`; - response += `ā€¢ 24h Volume: ${formatValue(data.volume24h)}\n`; - if (data.volumeChange24h !== undefined) { - response += `ā€¢ 24h Volume Change: ${formatValue(data.volumeChange24h)} (${formatPercentChange(data.volumeChange24hPercent)})\n`; - } - - response += `\nā° Last Updated: ${timestamp}`; - - return response; -}; - -const formatMultiplePriceVolumeResponse = ( - data: MultiPriceVolumeResponse, - chain: Chain -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - let response = `Price & Volume Data on ${chainName}:\n\n`; - - // Sort tokens by volume - const sortedTokens = Object.entries(data).sort((a, b) => { - const volumeA = a[1].volume24h || 0; - const volumeB = b[1].volume24h || 0; - return volumeB - volumeA; - }); - - sortedTokens.forEach(([address, tokenData]) => { - const timestamp = formatTimestamp(tokenData.timestamp); - const priceFormatted = formatValue(tokenData.price); - - response += `${tokenData.token} (${address.slice(0, 6)}...${address.slice(-4)})\n`; - response += `ā€¢ Price: ${priceFormatted}`; - if (tokenData.priceChange24hPercent !== undefined) { - response += ` (${formatPercentChange(tokenData.priceChange24hPercent)})`; - } - response += `\n`; - response += `ā€¢ Volume: ${formatValue(tokenData.volume24h)}`; - if (tokenData.volumeChange24hPercent !== undefined) { - response += ` (${formatPercentChange(tokenData.volumeChange24hPercent)})`; - } - response += `\n`; - response += `ā€¢ Updated: ${timestamp}\n\n`; - }); - - return response; -}; - -export const priceVolumeProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsPriceVolumeKeyword(messageText)) { - return null; - } - - const addresses = extractContractAddresses(messageText); - if (addresses.length === 0) { - return null; - } - - const chain = extractChain(messageText); - - if (addresses.length === 1) { - elizaLogger.info( - `PRICE/VOLUME provider activated for address ${addresses[0]} on ${chain}` - ); - - const priceVolumeData = await getSinglePriceVolume( - apiKey, - addresses[0], - chain - ); - - if (!priceVolumeData) { - return null; - } - - return formatSinglePriceVolumeResponse(priceVolumeData, chain); - } else { - elizaLogger.info( - `MULTIPLE PRICE/VOLUME provider activated for ${addresses.length} addresses on ${chain}` - ); - - const priceVolumeData = await getMultiplePriceVolume( - apiKey, - addresses, - chain - ); - - if (!priceVolumeData) { - return null; - } - - return formatMultiplePriceVolumeResponse(priceVolumeData, chain); - } - }, -}; diff --git a/packages/plugin-birdeye/src/providers/defi/token-trades-provider.ts b/packages/plugin-birdeye/src/providers/defi/token-trades-provider.ts deleted file mode 100644 index d580eb0df9..0000000000 --- a/packages/plugin-birdeye/src/providers/defi/token-trades-provider.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { - BASE_URL, - Chain, - extractChain, - extractContractAddresses, - extractLimit, - formatTimestamp, - formatValue, - makeApiRequest, - shortenAddress, -} from "../utils"; - -// Types -interface Trade { - timestamp: number; - price: number; - volume: number; - side: "buy" | "sell"; - source: string; - txHash: string; - buyer?: string; - seller?: string; -} - -interface TokenTradesResponse { - trades: Trade[]; - totalCount: number; - token: string; -} - -// Constants -const TOKEN_TRADES_KEYWORDS = [ - "token trades", - "token swaps", - "token transactions", - "token activity", - "token orders", - "token executions", - "token trading", - "token market activity", - "token exchange activity", - "token trading history", - "token market history", - "token exchange history", -] as const; - -// Helper functions -const containsTokenTradesKeyword = (text: string): boolean => { - return TOKEN_TRADES_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getTokenTrades = async ( - apiKey: string, - contractAddress: string, - chain: Chain, - limit: number -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - limit: limit.toString(), - }); - const url = `${BASE_URL}/defi/trades_token?${params.toString()}`; - - elizaLogger.info( - `Fetching token trades for ${contractAddress} on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { - apiKey, - chain, - }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching token trades:", error.message); - } - return null; - } -}; - -const formatTrade = (trade: Trade): string => { - const timestamp = formatTimestamp(trade.timestamp); - const side = trade.side === "buy" ? "šŸŸ¢ Buy" : "šŸ”“ Sell"; - - let response = `${side} - ${timestamp}\n`; - response += `ā€¢ Price: ${formatValue(trade.price)}\n`; - response += `ā€¢ Volume: ${formatValue(trade.volume)}\n`; - response += `ā€¢ Source: ${trade.source}\n`; - if (trade.buyer && trade.seller) { - response += `ā€¢ Buyer: ${shortenAddress(trade.buyer)}\n`; - response += `ā€¢ Seller: ${shortenAddress(trade.seller)}\n`; - } - response += `ā€¢ Tx: ${shortenAddress(trade.txHash)}`; - - return response; -}; - -const formatTokenTradesResponse = ( - data: TokenTradesResponse, - chain: Chain -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - - let response = `Recent Trades for ${data.token} on ${chainName}\n\n`; - - if (data.trades.length === 0) { - return response + "No trades found."; - } - - // Calculate summary statistics - const totalVolume = data.trades.reduce((sum, t) => sum + t.volume, 0); - const averageVolume = totalVolume / data.trades.length; - const buyCount = data.trades.filter((t) => t.side === "buy").length; - const buyRatio = (buyCount / data.trades.length) * 100; - const averagePrice = - data.trades.reduce((sum, t) => sum + t.price, 0) / data.trades.length; - const priceChange = - ((data.trades[data.trades.length - 1].price - data.trades[0].price) / - data.trades[0].price) * - 100; - const highestPrice = Math.max(...data.trades.map((t) => t.price)); - const lowestPrice = Math.min(...data.trades.map((t) => t.price)); - const priceRange = ((highestPrice - lowestPrice) / lowestPrice) * 100; - - response += `šŸ“Š Summary\n`; - response += `ā€¢ Total Trades: ${data.trades.length}\n`; - response += `ā€¢ Total Volume: ${formatValue(totalVolume)}\n`; - response += `ā€¢ Average Volume: ${formatValue(averageVolume)}\n`; - response += `ā€¢ Buy/Sell Ratio: ${buyRatio.toFixed(1)}% buys\n`; - response += `ā€¢ Average Price: ${formatValue(averagePrice)}\n`; - response += `ā€¢ Price Change: ${priceChange >= 0 ? "+" : ""}${priceChange.toFixed(2)}%\n`; - response += `ā€¢ Price Range: ${priceRange.toFixed(2)}%\n\n`; - - // Add market analysis - const volatility = priceRange / Math.sqrt(data.trades.length); - const volumePerTrade = totalVolume / data.trades.length; - let marketAnalysis = ""; - - if (data.trades.length < 5) { - marketAnalysis = "Insufficient data for detailed analysis"; - } else { - // Analyze trading activity - const activityLevel = - data.trades.length > 20 - ? "high" - : data.trades.length > 10 - ? "moderate" - : "low"; - const volumeLevel = - volumePerTrade > averageVolume * 2 - ? "high" - : volumePerTrade > averageVolume - ? "moderate" - : "low"; - const volatilityLevel = - volatility > 5 ? "high" : volatility > 2 ? "moderate" : "low"; - const trend = - Math.abs(priceChange) < 1 - ? "sideways" - : priceChange > 0 - ? "upward" - : "downward"; - - marketAnalysis = `Market shows ${activityLevel} trading activity with ${volumeLevel} volume per trade. `; - marketAnalysis += `${volatilityLevel.charAt(0).toUpperCase() + volatilityLevel.slice(1)} volatility with a ${trend} price trend.`; - } - - response += `šŸ“ˆ Market Analysis\n`; - response += `ā€¢ ${marketAnalysis}\n\n`; - - response += `šŸ”„ Recent Trades\n`; - data.trades.forEach((trade, index) => { - response += `${index + 1}. ${formatTrade(trade)}\n\n`; - }); - - if (data.totalCount > data.trades.length) { - response += `Showing ${data.trades.length} of ${data.totalCount} total trades.`; - } - - return response; -}; - -export const tokenTradesProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsTokenTradesKeyword(messageText)) { - return null; - } - - const addresses = extractContractAddresses(messageText); - if (addresses.length === 0) { - return null; - } - - const chain = extractChain(messageText); - const limit = extractLimit(messageText); - - elizaLogger.info( - `TOKEN TRADES provider activated for token ${addresses[0]} on ${chain}` - ); - - const tradesData = await getTokenTrades( - apiKey, - addresses[0], - chain, - limit - ); - - if (!tradesData) { - return null; - } - - return formatTokenTradesResponse(tradesData, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/defi/trades-seek-provider.ts b/packages/plugin-birdeye/src/providers/defi/trades-seek-provider.ts deleted file mode 100644 index 8ae96a1600..0000000000 --- a/packages/plugin-birdeye/src/providers/defi/trades-seek-provider.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { - BASE_URL, - Chain, - extractChain, - extractContractAddresses, - extractLimit, - extractTimeRange, - formatTimestamp, - formatValue, - makeApiRequest, - shortenAddress, -} from "../utils"; - -// Types -interface Trade { - timestamp: number; - price: number; - volume: number; - side: "buy" | "sell"; - source: string; - txHash: string; - buyer?: string; - seller?: string; -} - -interface TradesResponse { - trades: Trade[]; - totalCount: number; - token: string; -} - -// Constants -const TOKEN_TRADE_KEYWORDS = [ - "token trades", - "token trading", - "token transactions", - "token swaps", - "token buys", - "token sells", - "token orders", - "token executions", - "token trade history", - "token trading history", - "token recent trades", - "token market activity", - "token trading activity", - "token market trades", - "token exchange history", -] as const; - -// Helper functions -const containsTokenTradeKeyword = (text: string): boolean => { - return TOKEN_TRADE_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getTradesByTime = async ( - apiKey: string, - contractAddress: string, - timestamp: number, - chain: Chain, - limit: number -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - timestamp: timestamp.toString(), - limit: limit.toString(), - }); - const url = `${BASE_URL}/defi/trades_token_seek_time?${params.toString()}`; - - elizaLogger.info( - `Fetching trades for token ${contractAddress} since ${new Date( - timestamp * 1000 - ).toLocaleString()} on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { apiKey, chain }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching trades by time:", error.message); - } - return null; - } -}; - -const formatTrade = (trade: Trade): string => { - const timestamp = formatTimestamp(trade.timestamp); - const side = trade.side === "buy" ? "šŸŸ¢ Buy" : "šŸ”“ Sell"; - - let response = `${side} - ${timestamp}\n`; - response += `ā€¢ Price: ${formatValue(trade.price)}\n`; - response += `ā€¢ Volume: ${formatValue(trade.volume)}\n`; - response += `ā€¢ Source: ${trade.source}\n`; - if (trade.buyer && trade.seller) { - response += `ā€¢ Buyer: ${shortenAddress(trade.buyer)}\n`; - response += `ā€¢ Seller: ${shortenAddress(trade.seller)}\n`; - } - response += `ā€¢ Tx: ${shortenAddress(trade.txHash)}`; - - return response; -}; - -const formatTradesResponse = ( - data: TradesResponse, - timeRange: { start: number; end: number }, - chain: Chain -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - const startDate = formatTimestamp(timeRange.start); - const endDate = formatTimestamp(timeRange.end); - - let response = `Trade History for ${data.token} on ${chainName}\n`; - response += `Period: ${startDate} to ${endDate}\n\n`; - - if (data.trades.length === 0) { - return response + "No trades found in this time period."; - } - - // Calculate summary statistics - const totalVolume = data.trades.reduce((sum, t) => sum + t.volume, 0); - const averageVolume = totalVolume / data.trades.length; - const buyCount = data.trades.filter((t) => t.side === "buy").length; - const buyRatio = (buyCount / data.trades.length) * 100; - const averagePrice = - data.trades.reduce((sum, t) => sum + t.price, 0) / data.trades.length; - const priceChange = - ((data.trades[data.trades.length - 1].price - data.trades[0].price) / - data.trades[0].price) * - 100; - - response += `šŸ“Š Summary\n`; - response += `ā€¢ Total Trades: ${data.trades.length}\n`; - response += `ā€¢ Total Volume: ${formatValue(totalVolume)}\n`; - response += `ā€¢ Average Volume: ${formatValue(averageVolume)}\n`; - response += `ā€¢ Buy/Sell Ratio: ${buyRatio.toFixed(1)}% buys\n`; - response += `ā€¢ Average Price: ${formatValue(averagePrice)}\n`; - response += `ā€¢ Price Change: ${priceChange >= 0 ? "+" : ""}${priceChange.toFixed(2)}%\n\n`; - - response += `šŸ“ˆ Recent Trades\n`; - data.trades.forEach((trade, index) => { - response += `${index + 1}. ${formatTrade(trade)}\n\n`; - }); - - if (data.totalCount > data.trades.length) { - response += `Showing ${data.trades.length} of ${data.totalCount} total trades.`; - } - - return response; -}; - -export const tokenTradesSeekProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsTokenTradeKeyword(messageText)) { - return null; - } - - const addresses = extractContractAddresses(messageText); - if (addresses.length === 0) { - return null; - } - - const chain = extractChain(messageText); - const timeRange = extractTimeRange(messageText); - const limit = extractLimit(messageText); - - elizaLogger.info( - `TOKEN TRADES SEEK provider activated for token ${addresses[0]} from ${new Date( - timeRange.start * 1000 - ).toLocaleString()} to ${new Date( - timeRange.end * 1000 - ).toLocaleString()} on ${chain}` - ); - - const tradesData = await getTradesByTime( - apiKey, - addresses[0], - timeRange.start, - chain, - limit - ); - - if (!tradesData) { - return null; - } - - return formatTradesResponse(tradesData, timeRange, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/pair/index.ts b/packages/plugin-birdeye/src/providers/pair/index.ts deleted file mode 100644 index 92e68edb47..0000000000 --- a/packages/plugin-birdeye/src/providers/pair/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./pair-overview-provider"; diff --git a/packages/plugin-birdeye/src/providers/pair/pair-overview-provider.ts b/packages/plugin-birdeye/src/providers/pair/pair-overview-provider.ts deleted file mode 100644 index f169d59d1c..0000000000 --- a/packages/plugin-birdeye/src/providers/pair/pair-overview-provider.ts +++ /dev/null @@ -1,286 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; - -// Types -interface PairToken { - address: string; - symbol: string; - name: string; - decimals: number; - logoURI?: string; -} - -interface PairOverview { - address: string; - baseToken: PairToken; - quoteToken: PairToken; - price: number; - priceChange24h: number; - priceChange24hPercent: number; - volume24h: number; - liquidity: number; - txCount24h: number; - lastTradeUnixTime: number; - dex: string; -} - -interface MultiPairOverview { - [pairAddress: string]: PairOverview; -} - -// Constants -const PAIR_KEYWORDS = [ - "pair", - "pairs", - "trading pair", - "market", - "markets", - "pool", - "pools", - "liquidity pool", - "dex", - "exchange", -] as const; - -const CHAIN_KEYWORDS = [ - "solana", - "ethereum", - "arbitrum", - "avalanche", - "bsc", - "optimism", - "polygon", - "base", - "zksync", - "sui", -] as const; - -const BASE_URL = "https://public-api.birdeye.so"; - -// Helper functions -const containsPairKeyword = (text: string): boolean => { - return PAIR_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const extractChain = (text: string): string => { - const chain = CHAIN_KEYWORDS.find((chain) => - text.toLowerCase().includes(chain.toLowerCase()) - ); - return chain || "solana"; -}; - -const extractPairAddresses = (text: string): string[] => { - const words = text.split(/\s+/); - const addresses: string[] = []; - - for (const word of words) { - // Ethereum-like addresses (0x...) - if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { - addresses.push(word); - } - // Solana addresses (base58, typically 32-44 chars) - if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { - addresses.push(word); - } - } - return addresses; -}; - -const getPairOverview = async ( - apiKey: string, - pairAddress: string, - chain: string = "solana" -): Promise => { - try { - const params = new URLSearchParams({ - address: pairAddress, - }); - const url = `${BASE_URL}/pair/overview_single?${params.toString()}`; - - elizaLogger.info( - `Fetching pair overview for address ${pairAddress} on ${chain} from:`, - url - ); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - "x-chain": chain, - }, - }); - - if (!response.ok) { - if (response.status === 404) { - elizaLogger.warn(`Pair not found: ${pairAddress} on ${chain}`); - return null; - } - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - return data.data; - } catch (error) { - elizaLogger.error("Error fetching pair overview:", error); - return null; - } -}; - -const getMultiplePairOverviews = async ( - apiKey: string, - pairAddresses: string[], - chain: string = "solana" -): Promise => { - try { - const params = new URLSearchParams({ - addresses: pairAddresses.join(","), - }); - const url = `${BASE_URL}/pair/overview_multiple?${params.toString()}`; - - elizaLogger.info( - `Fetching multiple pair overviews for ${pairAddresses.length} pairs on ${chain} from:`, - url - ); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - "x-chain": chain, - }, - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - return data.data; - } catch (error) { - elizaLogger.error("Error fetching multiple pair overviews:", error); - return null; - } -}; - -const formatValue = (value: number): string => { - if (value >= 1_000_000_000) { - return `$${(value / 1_000_000_000).toFixed(2)}B`; - } - if (value >= 1_000_000) { - return `$${(value / 1_000_000).toFixed(2)}M`; - } - if (value >= 1_000) { - return `$${(value / 1_000).toFixed(2)}K`; - } - return `$${value.toFixed(2)}`; -}; - -const formatPairOverview = (pair: PairOverview): string => { - const lastTradeTime = new Date( - pair.lastTradeUnixTime * 1000 - ).toLocaleString(); - const priceFormatted = - pair.price < 0.01 ? pair.price.toExponential(2) : pair.price.toFixed(2); - - let response = `${pair.baseToken.symbol}/${pair.quoteToken.symbol} on ${pair.dex}\n`; - response += `ā€¢ Address: ${pair.address}\n`; - response += `ā€¢ Price: $${priceFormatted}\n`; - - const changeSymbol = pair.priceChange24h >= 0 ? "šŸ“ˆ" : "šŸ“‰"; - response += `ā€¢ 24h Change: ${changeSymbol} ${pair.priceChange24hPercent.toFixed(2)}% (${formatValue(pair.priceChange24h)})\n`; - response += `ā€¢ 24h Volume: ${formatValue(pair.volume24h)}\n`; - response += `ā€¢ Liquidity: ${formatValue(pair.liquidity)}\n`; - response += `ā€¢ 24h Transactions: ${pair.txCount24h.toLocaleString()}\n`; - response += `ā€¢ Last Trade: ${lastTradeTime}\n`; - - return response; -}; - -const formatMultiplePairOverviews = ( - pairs: MultiPairOverview, - chain: string -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - let response = `Trading Pairs on ${chainName}:\n\n`; - - if (Object.keys(pairs).length === 0) { - return response + "No pairs found."; - } - - // Sort pairs by liquidity - const sortedPairs = Object.values(pairs).sort( - (a, b) => b.liquidity - a.liquidity - ); - - sortedPairs.forEach((pair, index) => { - response += `${index + 1}. ${formatPairOverview(pair)}\n`; - }); - - return response; -}; - -export const pairOverviewProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsPairKeyword(messageText)) { - return null; - } - - const pairAddresses = extractPairAddresses(messageText); - if (pairAddresses.length === 0) { - return null; - } - - const chain = extractChain(messageText); - - if (pairAddresses.length === 1) { - elizaLogger.info( - `PAIR OVERVIEW provider activated for address ${pairAddresses[0]} on ${chain}` - ); - - const pairData = await getPairOverview( - apiKey, - pairAddresses[0], - chain - ); - - if (!pairData) { - return null; - } - - return formatPairOverview(pairData); - } else { - elizaLogger.info( - `MULTIPLE PAIR OVERVIEW provider activated for ${pairAddresses.length} pairs on ${chain}` - ); - - const pairData = await getMultiplePairOverviews( - apiKey, - pairAddresses, - chain - ); - - if (!pairData) { - return null; - } - - return formatMultiplePairOverviews(pairData, chain); - } - }, -}; diff --git a/packages/plugin-birdeye/src/providers/search/index.ts b/packages/plugin-birdeye/src/providers/search/index.ts deleted file mode 100644 index a27e735c05..0000000000 --- a/packages/plugin-birdeye/src/providers/search/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./token-market-data-provider"; diff --git a/packages/plugin-birdeye/src/providers/search/token-market-data-provider.ts b/packages/plugin-birdeye/src/providers/search/token-market-data-provider.ts deleted file mode 100644 index 29690ad1ea..0000000000 --- a/packages/plugin-birdeye/src/providers/search/token-market-data-provider.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; - -interface SearchToken { - address: string; - name: string; - symbol: string; - decimals: number; - volume24hUSD: number; - liquidity: number; - logoURI: string; - price: number; -} - -const SEARCH_KEYWORDS = ["search", "find", "look for", "lookup", "locate"]; - -const TOKEN_KEYWORDS = [ - "token", - "tokens", - "coin", - "coins", - "crypto", - "cryptocurrency", - "asset", - "assets", - "sol", - "solana", -]; - -const SUPPORTED_CHAINS = [ - "solana", - "ethereum", - "arbitrum", - "avalanche", - "bsc", - "optimism", - "polygon", - "base", - "zksync", - "sui", -]; - -const BASE_URL = "https://public-api.birdeye.so"; - -interface SearchTokensOptions { - query: string; - chain?: string; - limit?: number; - offset?: number; -} - -const searchTokens = async ( - apiKey: string, - options: SearchTokensOptions -): Promise => { - try { - const { query, chain = "solana", limit = 10, offset = 0 } = options; - - const params = new URLSearchParams({ - query, - limit: limit.toString(), - offset: offset.toString(), - }); - - const url = `${BASE_URL}/defi/v3/search?${params.toString()}`; - elizaLogger.info("Searching tokens from:", url); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - "x-chain": chain, - }, - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - return data.data.tokens || []; - } catch (error) { - elizaLogger.error("Error searching tokens:", error); - throw error; - } -}; - -const formatSearchResultsToString = ( - tokens: SearchToken[], - query: string, - chain: string -): string => { - if (!tokens.length) { - return `No tokens found matching "${query}" on ${chain}.`; - } - - const formattedTokens = tokens - .map((token, index) => { - const priceFormatted = - token.price != null - ? token.price < 0.01 - ? token.price.toExponential(2) - : token.price.toFixed(2) - : "N/A"; - - const volume = - token.volume24hUSD != null - ? `$${(token.volume24hUSD / 1_000_000).toFixed(2)}M` - : "N/A"; - - const liquidity = - token.liquidity != null - ? `$${(token.liquidity / 1_000_000).toFixed(2)}M` - : "N/A"; - - return ( - `${index + 1}. ${token.name} (${token.symbol}):\n` + - ` Address: ${token.address}\n` + - ` Price: $${priceFormatted}\n` + - ` Volume 24h: ${volume}\n` + - ` Liquidity: ${liquidity}` - ); - }) - .join("\n\n"); - - return `Search results for "${query}" on ${chain.charAt(0).toUpperCase() + chain.slice(1)}:\n\n${formattedTokens}`; -}; - -export const tokenMarketDataProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - return null; - } - - const messageText = message.content.text.toLowerCase(); - - // Check if message contains search-related keywords - const hasSearchKeyword = SEARCH_KEYWORDS.some((keyword) => - messageText.includes(keyword) - ); - - // Check if message contains token-related keywords - const hasTokenKeyword = TOKEN_KEYWORDS.some((keyword) => - messageText.includes(keyword) - ); - - // Extract potential search query - // Look for quotes first - let searchQuery = - messageText.match(/"([^"]+)"/)?.[1] || - messageText.match(/'([^']+)'/)?.[1]; - - // If no quotes, try to extract query after search keywords - if (!searchQuery) { - for (const keyword of SEARCH_KEYWORDS) { - if (messageText.includes(keyword)) { - const parts = messageText.split(keyword); - if (parts[1]) { - searchQuery = parts[1] - .trim() - .split(/[\s,.]/) - .filter((word) => word.length > 1) - .join(" ") - .trim(); - break; - } - } - } - } - - // Determine which chain is being asked about - const requestedChain = - SUPPORTED_CHAINS.find((chain) => - messageText.includes(chain.toLowerCase()) - ) || "solana"; - - // Get the current offset from state or default to 0 - const currentOffset = (_state?.searchTokensOffset as number) || 0; - - // Combine signals to make decision - const shouldProvideData = - searchQuery && hasSearchKeyword && hasTokenKeyword; - - if (!shouldProvideData || !searchQuery) { - return null; - } - - elizaLogger.info( - `Search tokens provider activated for query "${searchQuery}" on ${requestedChain}` - ); - - const searchResults = await searchTokens(apiKey, { - query: searchQuery, - chain: requestedChain, - offset: currentOffset, - limit: 10, - }); - - return formatSearchResultsToString( - searchResults, - searchQuery, - requestedChain - ); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/token/__tests__/token-overview-provider.test.ts b/packages/plugin-birdeye/src/providers/token/__tests__/token-overview-provider.test.ts deleted file mode 100644 index 85271a9fce..0000000000 --- a/packages/plugin-birdeye/src/providers/token/__tests__/token-overview-provider.test.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { IAgentRuntime, Memory, State } from "@elizaos/core"; -import { tokenOverviewProvider } from "../token-overview-provider"; - -// Mock data -const mockTokenOverview = { - address: "0x1234567890123456789012345678901234567890", - symbol: "TEST", - name: "Test Token", - decimals: 18, - logoURI: "https://example.com/logo.png", - price: 1.23, - priceChange24hPercent: 5.67, - liquidity: 1000000, - marketCap: 10000000, - realMc: 9000000, - supply: 1000000, - circulatingSupply: 900000, - holder: 1000, - v24h: 100000, - v24hUSD: 123000, - lastTradeUnixTime: 1704067200, - numberMarkets: 5, - extensions: { - website: "https://example.com", - twitter: "https://twitter.com/test", - telegram: "https://t.me/test", - discord: "https://discord.gg/test", - description: "A test token", - coingeckoId: "test-token", - }, -}; - -// Mock fetch globally -global.fetch = jest.fn(); - -describe("Token Overview Provider", () => { - let mockRuntime: IAgentRuntime; - let mockMessage: Memory; - let mockState: State; - - beforeEach(() => { - // Reset mocks - jest.clearAllMocks(); - - // Mock runtime - mockRuntime = { - getSetting: jest.fn().mockReturnValue("mock-api-key"), - } as unknown as IAgentRuntime; - - // Mock message - mockMessage = { - content: { - text: "Show me overview of 0x1234567890123456789012345678901234567890 on ethereum", - }, - } as Memory; - - // Mock state - mockState = {} as State; - - // Mock successful fetch response - (global.fetch as jest.Mock).mockResolvedValue({ - ok: true, - json: async () => ({ data: mockTokenOverview }), - }); - }); - - test("returns null when API key is missing", async () => { - (mockRuntime.getSetting as jest.Mock).mockReturnValue(null); - const result = await tokenOverviewProvider.get( - mockRuntime, - mockMessage, - mockState - ); - expect(result).toBeNull(); - }); - - test("returns null when message does not contain overview keywords", async () => { - mockMessage.content.text = "random message without overview keywords"; - const result = await tokenOverviewProvider.get( - mockRuntime, - mockMessage, - mockState - ); - expect(result).toBeNull(); - }); - - test("returns null when no contract address is found", async () => { - mockMessage.content.text = "show overview of invalid-address"; - const result = await tokenOverviewProvider.get( - mockRuntime, - mockMessage, - mockState - ); - expect(result).toBeNull(); - }); - - test("handles API error gracefully", async () => { - (global.fetch as jest.Mock).mockRejectedValue(new Error("API Error")); - const result = await tokenOverviewProvider.get( - mockRuntime, - mockMessage, - mockState - ); - expect(result).toBeNull(); - }); - - test("handles 404 response gracefully", async () => { - (global.fetch as jest.Mock).mockResolvedValue({ - ok: false, - status: 404, - }); - const result = await tokenOverviewProvider.get( - mockRuntime, - mockMessage, - mockState - ); - expect(result).toBeNull(); - }); - - test("formats token overview correctly", async () => { - const result = await tokenOverviewProvider.get( - mockRuntime, - mockMessage, - mockState - ); - - // Verify the result contains all expected sections - expect(result).toContain("Token Overview for Test Token (TEST)"); - expect(result).toContain("šŸ“Š Market Data"); - expect(result).toContain("šŸ“ˆ Trading Info"); - expect(result).toContain("šŸ’° Supply Information"); - expect(result).toContain("šŸ”— Token Details"); - expect(result).toContain("šŸŒ Social Links"); - - // Verify specific data points - expect(result).toContain(`Current Price: $${mockTokenOverview.price}`); - expect(result).toContain(`Market Cap: $${mockTokenOverview.marketCap}`); - expect(result).toContain(mockTokenOverview.address); - expect(result).toContain(mockTokenOverview.extensions.website); - }); - - test("handles missing social links gracefully", async () => { - const tokenWithoutSocials = { - ...mockTokenOverview, - extensions: undefined, - }; - (global.fetch as jest.Mock).mockResolvedValue({ - ok: true, - json: async () => ({ data: tokenWithoutSocials }), - }); - - const result = await tokenOverviewProvider.get( - mockRuntime, - mockMessage, - mockState - ); - expect(result).not.toContain("šŸŒ Social Links"); - }); - - test("extracts chain correctly", async () => { - mockMessage.content.text = - "show overview of 0x1234567890123456789012345678901234567890 on ethereum"; - await tokenOverviewProvider.get(mockRuntime, mockMessage, mockState); - - expect(global.fetch).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ - headers: expect.objectContaining({ - "x-chain": "ethereum", - }), - }) - ); - }); - - test("defaults to solana chain when not specified", async () => { - mockMessage.content.text = - "show overview of 0x1234567890123456789012345678901234567890"; - await tokenOverviewProvider.get(mockRuntime, mockMessage, mockState); - - expect(global.fetch).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ - headers: expect.objectContaining({ - "x-chain": "solana", - }), - }) - ); - }); -}); diff --git a/packages/plugin-birdeye/src/providers/token/all-market-list-provider.ts b/packages/plugin-birdeye/src/providers/token/all-market-list-provider.ts deleted file mode 100644 index dd7d9d8e93..0000000000 --- a/packages/plugin-birdeye/src/providers/token/all-market-list-provider.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { BASE_URL, Chain, makeApiRequest } from "../utils"; - -// Types -interface Market { - address: string; - name: string; - symbol: string; - baseToken: string; - quoteToken: string; - volume24h: number; - tvl: number; - lastTradeTime: number; -} - -interface AllMarketsResponse { - markets: Market[]; -} - -// Constants -const MARKET_LIST_KEYWORDS = [ - "all markets", - "market list", - "trading pairs", - "available markets", - "list markets", - "show markets", -] as const; - -// Helper functions -const containsMarketListKeyword = (text: string): boolean => { - return MARKET_LIST_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getAllMarkets = async ( - apiKey: string, - chain: Chain = "solana" -): Promise => { - try { - const url = `${BASE_URL}/token/all_market_list`; - - elizaLogger.info("Fetching all markets from:", url); - - return await makeApiRequest(url, { apiKey, chain }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching markets:", error.message); - } - return null; - } -}; - -const formatAllMarketsResponse = (data: AllMarketsResponse): string => { - let response = "šŸ“Š Available Markets\n\n"; - - // Sort markets by volume - const sortedMarkets = [...data.markets].sort( - (a, b) => b.volume24h - a.volume24h - ); - - sortedMarkets.forEach((market) => { - const lastTradeDate = new Date( - market.lastTradeTime * 1000 - ).toLocaleString(); - - response += `${market.name} (${market.symbol})\n`; - response += `ā€¢ Address: ${market.address}\n`; - response += `ā€¢ Base Token: ${market.baseToken}\n`; - response += `ā€¢ Quote Token: ${market.quoteToken}\n`; - response += `ā€¢ 24h Volume: $${market.volume24h.toLocaleString()}\n`; - response += `ā€¢ TVL: $${market.tvl.toLocaleString()}\n`; - response += `ā€¢ Last Trade: ${lastTradeDate}\n\n`; - }); - - return response.trim(); -}; - -export const allMarketListProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsMarketListKeyword(messageText)) { - return null; - } - - elizaLogger.info("ALL_MARKET_LIST provider activated"); - - const marketsData = await getAllMarkets(apiKey); - - if (!marketsData) { - return null; - } - - return formatAllMarketsResponse(marketsData); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/token/index.ts b/packages/plugin-birdeye/src/providers/token/index.ts deleted file mode 100644 index 1fdabaf640..0000000000 --- a/packages/plugin-birdeye/src/providers/token/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -export * from "./all-market-list-provider"; -export * from "./new-listing-provider"; -export * from "./token-creation-provider"; -export * from "./token-holder-provider"; -export * from "./token-list-provider"; -export * from "./token-market-provider"; -export * from "./token-metadata-provider"; -export * from "./token-mint-burn-provider"; -export * from "./token-overview-provider"; -export * from "./token-security-provider"; -export * from "./token-trade-provider"; -export * from "./top-traders-provider"; -export * from "./trending-tokens-provider"; diff --git a/packages/plugin-birdeye/src/providers/token/new-listing-provider.ts b/packages/plugin-birdeye/src/providers/token/new-listing-provider.ts deleted file mode 100644 index 5ee8952577..0000000000 --- a/packages/plugin-birdeye/src/providers/token/new-listing-provider.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { BASE_URL, Chain, makeApiRequest } from "../utils"; - -// Types -interface TokenListing { - address: string; - name: string; - symbol: string; - listingTime: number; - initialPrice: number; - currentPrice: number; - priceChange: number; - volume24h: number; -} - -interface NewListingsResponse { - listings: TokenListing[]; -} - -// Constants -const NEW_LISTING_KEYWORDS = [ - "new listings", - "newly listed", - "recent listings", - "latest tokens", - "new tokens", -] as const; - -// Helper functions -const containsNewListingKeyword = (text: string): boolean => { - return NEW_LISTING_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getNewListings = async ( - apiKey: string, - chain: Chain = "solana" -): Promise => { - try { - const url = `${BASE_URL}/token/new_listing`; - - elizaLogger.info("Fetching new token listings from:", url); - - return await makeApiRequest(url, { - apiKey, - chain, - }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching new listings:", error.message); - } - return null; - } -}; - -const formatNewListingsResponse = (data: NewListingsResponse): string => { - let response = "šŸ†• New Token Listings\n\n"; - - data.listings.forEach((listing) => { - const listingDate = new Date( - listing.listingTime * 1000 - ).toLocaleString(); - const priceChangePercent = (listing.priceChange * 100).toFixed(2); - const priceChangeEmoji = listing.priceChange >= 0 ? "šŸ“ˆ" : "šŸ“‰"; - - response += `${listing.name} (${listing.symbol}) ${priceChangeEmoji}\n`; - response += `ā€¢ Address: ${listing.address}\n`; - response += `ā€¢ Listed: ${listingDate}\n`; - response += `ā€¢ Initial Price: $${listing.initialPrice.toFixed(6)}\n`; - response += `ā€¢ Current Price: $${listing.currentPrice.toFixed(6)}\n`; - response += `ā€¢ Price Change: ${priceChangePercent}%\n`; - response += `ā€¢ 24h Volume: $${listing.volume24h.toLocaleString()}\n\n`; - }); - - return response.trim(); -}; - -export const newListingProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsNewListingKeyword(messageText)) { - return null; - } - - elizaLogger.info("NEW_LISTING provider activated"); - - const listingsData = await getNewListings(apiKey); - - if (!listingsData) { - return null; - } - - return formatNewListingsResponse(listingsData); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/token/token-creation-provider.ts b/packages/plugin-birdeye/src/providers/token/token-creation-provider.ts deleted file mode 100644 index fc78c81f39..0000000000 --- a/packages/plugin-birdeye/src/providers/token/token-creation-provider.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { - BASE_URL, - Chain, - extractChain, - extractContractAddresses, - formatTimestamp, - formatValue, - makeApiRequest, - shortenAddress, -} from "../utils"; - -// Types -interface CreationData { - creator: string; - creatorBalance: number; - creatorBalanceUSD: number; - creatorShare: number; - creationTime: number; - initialSupply: number; - initialSupplyUSD: number; - creationTx: string; -} - -interface CreationResponse { - data: CreationData; - token: string; -} - -// Constants -const CREATION_KEYWORDS = [ - "creation", - "creator", - "created", - "launch", - "launched", - "deployment", - "deployed", - "initial supply", - "token creation", - "token launch", - "token deployment", - "token origin", - "token history", - "token birth", - "genesis", -] as const; - -// Helper functions -const containsCreationKeyword = (text: string): boolean => { - return CREATION_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getTokenCreation = async ( - apiKey: string, - contractAddress: string, - chain: Chain -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - }); - const url = `${BASE_URL}/token/creation?${params.toString()}`; - - elizaLogger.info( - `Fetching creation data for ${contractAddress} on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { apiKey, chain }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching creation data:", error.message); - } - return null; - } -}; - -const analyzeCreationMetrics = (data: CreationData): string => { - let analysis = ""; - - // Analyze creator's share - if (data.creatorShare > 50) { - analysis += - "āš ļø Creator holds majority of supply, high concentration risk. "; - } else if (data.creatorShare > 20) { - analysis += "āš” Creator maintains significant holdings. "; - } else if (data.creatorShare > 5) { - analysis += "āœ… Creator retains moderate holdings. "; - } else { - analysis += "šŸ”„ Creator holds minimal share of supply. "; - } - - // Analyze initial supply value - if (data.initialSupplyUSD > 1000000) { - analysis += - "šŸ’° Large initial supply value indicates significant launch. "; - } else if (data.initialSupplyUSD > 100000) { - analysis += - "šŸ’« Moderate initial supply value suggests standard launch. "; - } else { - analysis += - "šŸŒ± Small initial supply value indicates grassroots launch. "; - } - - // Analyze creator's current position - const valueChange = data.creatorBalanceUSD / data.initialSupplyUSD; - if (valueChange > 1.5) { - analysis += "šŸ“ˆ Creator's position has significantly appreciated. "; - } else if (valueChange < 0.5) { - analysis += "šŸ“‰ Creator's position has notably decreased. "; - } - - return analysis; -}; - -const formatCreationResponse = ( - data: CreationResponse, - chain: Chain -): string => { - const { data: creationData } = data; - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - - let response = `Token Creation Data for ${data.token} on ${chainName}\n\n`; - - // Creation Analysis - response += "šŸ“Š Creation Analysis\n"; - response += analyzeCreationMetrics(creationData) + "\n\n"; - - // Creation Details - response += "šŸŽ‚ Creation Details\n"; - response += `Creation Time: ${formatTimestamp(creationData.creationTime)}\n`; - response += `Creator: ${shortenAddress(creationData.creator)}\n`; - response += `Creation Tx: ${shortenAddress(creationData.creationTx)}\n\n`; - - // Supply Information - response += "šŸ’° Supply Information\n"; - response += `Initial Supply: ${formatValue(creationData.initialSupply)}\n`; - response += `Initial Value: ${formatValue(creationData.initialSupplyUSD)}\n\n`; - - // Creator Holdings - response += "šŸ‘¤ Creator Holdings\n"; - response += `Current Balance: ${formatValue(creationData.creatorBalance)}\n`; - response += `Current Value: ${formatValue(creationData.creatorBalanceUSD)}\n`; - response += `Share of Supply: ${creationData.creatorShare.toFixed(2)}%`; - - return response; -}; - -export const tokenCreationProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsCreationKeyword(messageText)) { - return null; - } - - const addresses = extractContractAddresses(messageText); - if (addresses.length === 0) { - return null; - } - - const chain = extractChain(messageText); - - elizaLogger.info( - `TOKEN CREATION provider activated for ${addresses[0]} on ${chain}` - ); - - const creationData = await getTokenCreation( - apiKey, - addresses[0], - chain - ); - - if (!creationData) { - return null; - } - - return formatCreationResponse(creationData, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/token/token-holder-provider.ts b/packages/plugin-birdeye/src/providers/token/token-holder-provider.ts deleted file mode 100644 index 5f9c40b369..0000000000 --- a/packages/plugin-birdeye/src/providers/token/token-holder-provider.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { - BASE_URL, - Chain, - extractChain, - extractContractAddresses, - extractLimit, - formatValue, - makeApiRequest, - shortenAddress, -} from "../utils"; - -// Types -interface HolderData { - address: string; - balance: number; - balanceUSD: number; - share: number; - rank: number; -} - -interface TokenHolderResponse { - holders: HolderData[]; - totalCount: number; - token: string; -} - -// Constants -const HOLDER_KEYWORDS = [ - "holders", - "holding", - "token holders", - "token holding", - "who holds", - "who owns", - "ownership", - "distribution", - "token distribution", - "token ownership", - "top holders", - "largest holders", - "biggest holders", - "whale holders", - "whale watching", -] as const; - -// Helper functions -const containsHolderKeyword = (text: string): boolean => { - return HOLDER_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getTokenHolders = async ( - apiKey: string, - contractAddress: string, - chain: Chain, - limit: number -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - limit: limit.toString(), - }); - const url = `${BASE_URL}/token/holder?${params.toString()}`; - - elizaLogger.info( - `Fetching token holders for ${contractAddress} on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { - apiKey, - chain, - }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching token holders:", error.message); - } - return null; - } -}; - -const formatHolderData = (holder: HolderData): string => { - let response = `${holder.rank}. ${shortenAddress(holder.address)}\n`; - response += ` ā€¢ Balance: ${holder.balance ? formatValue(holder.balance) : "N/A"}\n`; - response += ` ā€¢ Value: ${holder.balanceUSD ? formatValue(holder.balanceUSD) : "N/A"}\n`; - response += ` ā€¢ Share: ${holder.share ? holder.share.toFixed(2) : "0.00"}%`; - return response; -}; - -const analyzeDistribution = (holders: HolderData[]): string => { - // Calculate concentration metrics - const top10Share = holders - .slice(0, 10) - .reduce((sum, h) => sum + h.share, 0); - const top20Share = holders - .slice(0, 20) - .reduce((sum, h) => sum + h.share, 0); - const top50Share = holders - .slice(0, 50) - .reduce((sum, h) => sum + h.share, 0); - - let analysis = ""; - - // Analyze top holder concentration - const topHolder = holders[0]; - if (topHolder.share > 50) { - analysis += - "šŸšØ Extremely high concentration: Top holder owns majority of supply. "; - } else if (topHolder.share > 20) { - analysis += - "āš ļø High concentration: Top holder owns significant portion. "; - } else if (topHolder.share > 10) { - analysis += - "ā„¹ļø Moderate concentration: Top holder owns notable portion. "; - } else { - analysis += - "āœ… Good distribution: No single holder owns dominant share. "; - } - - // Analyze overall distribution - if (top10Share > 80) { - analysis += - "Top 10 holders control vast majority of supply, indicating high centralization. "; - } else if (top10Share > 50) { - analysis += - "Top 10 holders control majority of supply, showing moderate centralization. "; - } else { - analysis += - "Top 10 holders control less than half of supply, suggesting good distribution. "; - } - - // Provide distribution metrics - analysis += `\n\nDistribution Metrics:\n`; - analysis += `ā€¢ Top 10 Holders: ${top10Share.toFixed(2)}%\n`; - analysis += `ā€¢ Top 20 Holders: ${top20Share.toFixed(2)}%\n`; - analysis += `ā€¢ Top 50 Holders: ${top50Share.toFixed(2)}%`; - - return analysis; -}; - -const formatHolderResponse = ( - data: TokenHolderResponse, - chain: Chain -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - - let response = `Token Holders for ${data.token} on ${chainName}\n\n`; - - if (data.holders.length === 0) { - return response + "No holder data found."; - } - - response += `šŸ“Š Distribution Analysis\n`; - response += analyzeDistribution(data.holders); - response += "\n\n"; - - response += `šŸ‘„ Top Holders\n`; - data.holders.forEach((holder) => { - response += formatHolderData(holder) + "\n\n"; - }); - - if (data.totalCount > data.holders.length) { - response += `Showing ${data.holders.length} of ${data.totalCount} total holders.`; - } - - return response; -}; - -export const tokenHolderProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsHolderKeyword(messageText)) { - return null; - } - - const addresses = extractContractAddresses(messageText); - if (addresses.length === 0) { - return null; - } - - const chain = extractChain(messageText); - const limit = extractLimit(messageText); - - elizaLogger.info( - `TOKEN HOLDER provider activated for ${addresses[0]} on ${chain}` - ); - - const holderData = await getTokenHolders( - apiKey, - addresses[0], - chain, - limit - ); - - if (!holderData) { - return null; - } - - return formatHolderResponse(holderData, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/token/token-list-provider.ts b/packages/plugin-birdeye/src/providers/token/token-list-provider.ts deleted file mode 100644 index dfe95705b2..0000000000 --- a/packages/plugin-birdeye/src/providers/token/token-list-provider.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { - BASE_URL, - Chain, - extractChain, - formatPercentChange, - formatValue, - makeApiRequest, - shortenAddress, -} from "../utils"; - -// Types -interface TokenListData { - address: string; - name: string; - symbol: string; - decimals: number; - price: number; - priceChange24h: number; - volume24h: number; - marketCap: number; - liquidity: number; - rank: number; -} - -interface TokenListResponse { - data: TokenListData[]; - totalCount: number; -} - -// Constants -const LIST_KEYWORDS = [ - "list", - "top tokens", - "popular tokens", - "trending tokens", - "token list", - "token ranking", - "token rankings", - "token leaderboard", - "best tokens", - "highest volume", - "highest market cap", - "highest liquidity", -] as const; - -// Helper functions -const containsListKeyword = (text: string): boolean => { - return LIST_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getTokenList = async ( - apiKey: string, - chain: Chain, - limit: number = 10 -): Promise => { - try { - const params = new URLSearchParams({ - limit: limit.toString(), - }); - const url = `${BASE_URL}/token/list?${params.toString()}`; - - elizaLogger.info(`Fetching token list on ${chain} from:`, url); - - return await makeApiRequest(url, { apiKey, chain }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching token list:", error.message); - } - return null; - } -}; - -const formatTokenData = (token: TokenListData, rank: number): string => { - let response = `${rank}. ${token.name} (${token.symbol})\n`; - response += ` ā€¢ Address: ${shortenAddress(token.address)}\n`; - response += ` ā€¢ Price: ${formatValue(token.price)} (${formatPercentChange(token.priceChange24h)})\n`; - response += ` ā€¢ Volume 24h: ${formatValue(token.volume24h)}\n`; - response += ` ā€¢ Market Cap: ${formatValue(token.marketCap)}\n`; - response += ` ā€¢ Liquidity: ${formatValue(token.liquidity)}`; - return response; -}; - -const analyzeTokenList = (tokens: TokenListData[]): string => { - let analysis = ""; - - // Volume analysis - const validVolumes = tokens.filter((t) => t.volume24h != null); - const totalVolume = validVolumes.reduce((sum, t) => sum + t.volume24h, 0); - const avgVolume = - validVolumes.length > 0 ? totalVolume / validVolumes.length : 0; - const highVolumeTokens = validVolumes.filter( - (t) => t.volume24h > avgVolume * 2 - ); - - if (highVolumeTokens.length > 0) { - analysis += `šŸ”„ ${highVolumeTokens.length} tokens showing exceptional trading activity.\n`; - } - - // Price movement analysis - const validPriceChanges = tokens.filter((t) => t.priceChange24h != null); - const positiveMovers = validPriceChanges.filter( - (t) => t.priceChange24h > 0 - ); - const strongMovers = validPriceChanges.filter( - (t) => Math.abs(t.priceChange24h) > 10 - ); - - if (validPriceChanges.length > 0) { - if (positiveMovers.length > validPriceChanges.length / 2) { - analysis += - "šŸ“ˆ Market showing bullish trend with majority positive price movement.\n"; - } else { - analysis += - "šŸ“‰ Market showing bearish trend with majority negative price movement.\n"; - } - - if (strongMovers.length > 0) { - analysis += `āš” ${strongMovers.length} tokens with significant price movement (>10%).\n`; - } - } - - // Liquidity analysis - const totalLiquidity = tokens.reduce((sum, t) => sum + t.liquidity, 0); - const avgLiquidity = totalLiquidity / tokens.length; - const highLiquidityTokens = tokens.filter( - (t) => t.liquidity > avgLiquidity * 2 - ); - - if (highLiquidityTokens.length > 0) { - analysis += `šŸ’§ ${highLiquidityTokens.length} tokens with notably high liquidity.\n`; - } - - return analysis; -}; - -const formatListResponse = (data: TokenListResponse, chain: Chain): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - - let response = `Top Tokens on ${chainName}\n\n`; - - // Market Analysis - response += "šŸ“Š Market Analysis\n"; - response += analyzeTokenList(data.data) + "\n\n"; - - // Token List - response += "šŸ† Token Rankings\n"; - data.data.forEach((token, index) => { - response += formatTokenData(token, index + 1) + "\n\n"; - }); - - if (data.totalCount > data.data.length) { - response += `Showing ${data.data.length} of ${data.totalCount} total tokens.`; - } - - return response; -}; - -export const tokenListProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsListKeyword(messageText)) { - return null; - } - - const chain = extractChain(messageText); - const limit = messageText.toLowerCase().includes("all") ? 100 : 10; - - elizaLogger.info(`TOKEN LIST provider activated for ${chain}`); - - const listData = await getTokenList(apiKey, chain, limit); - - if (!listData) { - return null; - } - - return formatListResponse(listData, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/token/token-market-provider.ts b/packages/plugin-birdeye/src/providers/token/token-market-provider.ts deleted file mode 100644 index a5f2ccb85f..0000000000 --- a/packages/plugin-birdeye/src/providers/token/token-market-provider.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { - BASE_URL, - Chain, - extractChain, - extractContractAddresses, - formatPercentChange, - formatValue, - makeApiRequest, -} from "../utils"; - -// Types -interface MarketData { - price: number; - priceChange24h: number; - priceChange7d: number; - priceChange14d: number; - priceChange30d: number; - volume24h: number; - volume7d: number; - marketCap: number; - fullyDilutedValuation: number; - rank: number; - liquidity: number; - liquidityChange24h: number; - liquidityChange7d: number; -} - -interface MarketResponse { - data: MarketData; - token: string; -} - -// Constants -const MARKET_KEYWORDS = [ - "market", - "price", - "volume", - "liquidity", - "market cap", - "mcap", - "fdv", - "valuation", - "market data", - "market info", - "market stats", - "market metrics", - "market overview", - "market analysis", - "price change", - "price movement", - "price action", - "price performance", -] as const; - -// Helper functions -const containsMarketKeyword = (text: string): boolean => { - return MARKET_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getTokenMarket = async ( - apiKey: string, - contractAddress: string, - chain: Chain -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - }); - const url = `${BASE_URL}/token/market?${params.toString()}`; - - elizaLogger.info( - `Fetching market data for ${contractAddress} on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { apiKey, chain }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching market data:", error.message); - } - return null; - } -}; - -const formatPriceChanges = (data: MarketData): string => { - let changes = ""; - changes += `24h: ${formatPercentChange(data.priceChange24h)}\n`; - changes += `7d: ${formatPercentChange(data.priceChange7d)}\n`; - changes += `14d: ${formatPercentChange(data.priceChange14d)}\n`; - changes += `30d: ${formatPercentChange(data.priceChange30d)}`; - return changes; -}; - -const formatLiquidityChanges = (data: MarketData): string => { - let changes = ""; - changes += `24h: ${formatPercentChange(data.liquidityChange24h)}\n`; - changes += `7d: ${formatPercentChange(data.liquidityChange7d)}`; - return changes; -}; - -const analyzeMarketMetrics = (data: MarketData): string => { - let analysis = ""; - - // Price trend analysis - if (data.priceChange24h > 5) { - analysis += "šŸ“ˆ Strong bullish momentum in the last 24 hours. "; - } else if (data.priceChange24h < -5) { - analysis += "šŸ“‰ Significant price decline in the last 24 hours. "; - } - - // Volume analysis - const volumeToMcap = (data.volume24h / data.marketCap) * 100; - if (volumeToMcap > 20) { - analysis += "šŸ”„ High trading activity relative to market cap. "; - } else if (volumeToMcap < 1) { - analysis += "āš ļø Low trading volume relative to market cap. "; - } - - // Liquidity analysis - const liquidityToMcap = (data.liquidity / data.marketCap) * 100; - if (liquidityToMcap > 30) { - analysis += "šŸ’§ Strong liquidity relative to market cap. "; - } else if (liquidityToMcap < 5) { - analysis += "āš ļø Limited liquidity relative to market cap. "; - } - - // Market cap vs FDV analysis - if (data.fullyDilutedValuation > data.marketCap * 3) { - analysis += "āš ļø High potential for dilution based on FDV. "; - } - - return analysis || "Market metrics are within normal ranges."; -}; - -const formatMarketResponse = (data: MarketResponse, chain: Chain): string => { - const { data: marketData } = data; - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - - let response = `Market Data for ${data.token} on ${chainName}\n\n`; - - // Market Analysis - response += "šŸ“Š Market Analysis\n"; - response += analyzeMarketMetrics(marketData) + "\n\n"; - - // Price Information - response += "šŸ’° Price Information\n"; - response += `Current Price: ${formatValue(marketData.price)}\n\n`; - response += "Price Changes:\n"; - response += formatPriceChanges(marketData) + "\n\n"; - - // Volume Information - response += "šŸ“ˆ Volume Information\n"; - response += `24h Volume: ${marketData.volume24h ? formatValue(marketData.volume24h) : "N/A"}\n`; - response += `7d Volume: ${marketData.volume7d ? formatValue(marketData.volume7d) : "N/A"}\n\n`; - - // Market Metrics - response += "šŸ“Š Market Metrics\n"; - response += `Market Cap: ${marketData.marketCap ? formatValue(marketData.marketCap) : "N/A"}\n`; - response += `Fully Diluted Valuation: ${marketData.fullyDilutedValuation ? formatValue(marketData.fullyDilutedValuation) : "N/A"}\n`; - response += `Market Rank: ${marketData.rank ? `#${marketData.rank}` : "N/A"}\n\n`; - - // Liquidity Information - response += "šŸ’§ Liquidity Information\n"; - response += `Current Liquidity: ${marketData.liquidity ? formatValue(marketData.liquidity) : "N/A"}\n\n`; - response += "Liquidity Changes:\n"; - response += formatLiquidityChanges(marketData); - - return response; -}; - -export const tokenMarketProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsMarketKeyword(messageText)) { - return null; - } - - const addresses = extractContractAddresses(messageText); - if (addresses.length === 0) { - return null; - } - - const chain = extractChain(messageText); - - elizaLogger.info( - `TOKEN MARKET provider activated for ${addresses[0]} on ${chain}` - ); - - const marketData = await getTokenMarket(apiKey, addresses[0], chain); - - if (!marketData) { - return null; - } - - return formatMarketResponse(marketData, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/token/token-metadata-provider.ts b/packages/plugin-birdeye/src/providers/token/token-metadata-provider.ts deleted file mode 100644 index 590b70327c..0000000000 --- a/packages/plugin-birdeye/src/providers/token/token-metadata-provider.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { - BASE_URL, - Chain, - extractChain, - extractContractAddresses, - formatValue, - makeApiRequest, -} from "../utils"; - -// Types -interface TokenMetadata { - address: string; - name: string; - symbol: string; - decimals: number; - totalSupply: number; - totalSupplyUSD: number; - website: string; - twitter: string; - telegram: string; - discord: string; - coingeckoId: string; - description: string; - logo: string; - tags: string[]; -} - -interface MetadataResponse { - metadata: TokenMetadata; -} - -// Constants -const METADATA_KEYWORDS = [ - "metadata", - "token info", - "token information", - "token details", - "token data", - "token description", - "token profile", - "token overview", - "token stats", - "token statistics", - "token social", - "token links", - "token website", - "token socials", -] as const; - -// Helper functions -const containsMetadataKeyword = (text: string): boolean => { - return METADATA_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getTokenMetadata = async ( - apiKey: string, - contractAddress: string, - chain: Chain -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - }); - const url = `${BASE_URL}/token/metadata?${params.toString()}`; - - elizaLogger.info( - `Fetching token metadata for ${contractAddress} on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { apiKey, chain }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching token metadata:", error.message); - } - return null; - } -}; - -const formatSocialLinks = (metadata: TokenMetadata): string => { - const links = []; - - if (metadata.website) { - links.push(`šŸŒ [Website](${metadata.website})`); - } - if (metadata.twitter) { - links.push(`šŸ¦ [Twitter](${metadata.twitter})`); - } - if (metadata.telegram) { - links.push(`šŸ“± [Telegram](${metadata.telegram})`); - } - if (metadata.discord) { - links.push(`šŸ’¬ [Discord](${metadata.discord})`); - } - if (metadata.coingeckoId) { - links.push( - `šŸ¦Ž [CoinGecko](https://www.coingecko.com/en/coins/${metadata.coingeckoId})` - ); - } - - return links.length > 0 ? links.join("\n") : "No social links available"; -}; - -const formatMetadataResponse = ( - data: MetadataResponse, - chain: Chain -): string => { - const { metadata } = data; - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - - let response = `Token Metadata for ${metadata.name} (${metadata.symbol}) on ${chainName}\n\n`; - - // Basic Information - response += "šŸ“ Basic Information\n"; - response += `ā€¢ Name: ${metadata.name}\n`; - response += `ā€¢ Symbol: ${metadata.symbol}\n`; - response += `ā€¢ Address: ${metadata.address}\n`; - response += `ā€¢ Decimals: ${metadata.decimals}\n`; - response += `ā€¢ Total Supply: ${formatValue(metadata.totalSupply)}\n`; - response += `ā€¢ Total Supply USD: ${formatValue(metadata.totalSupplyUSD)}\n`; - - // Description - if (metadata.description) { - response += "\nšŸ“‹ Description\n"; - response += metadata.description + "\n"; - } - - // Tags - if (metadata.tags && metadata.tags.length > 0) { - response += "\nšŸ·ļø Tags\n"; - response += metadata.tags.map((tag) => `#${tag}`).join(" ") + "\n"; - } - - // Social Links - response += "\nšŸ”— Social Links\n"; - response += formatSocialLinks(metadata) + "\n"; - - // Logo - if (metadata.logo) { - response += "\nšŸ–¼ļø Logo\n"; - response += metadata.logo; - } - - return response; -}; - -export const tokenMetadataProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsMetadataKeyword(messageText)) { - return null; - } - - const addresses = extractContractAddresses(messageText); - if (addresses.length === 0) { - return null; - } - - const chain = extractChain(messageText); - - elizaLogger.info( - `TOKEN METADATA provider activated for ${addresses[0]} on ${chain}` - ); - - const metadataData = await getTokenMetadata( - apiKey, - addresses[0], - chain - ); - - if (!metadataData) { - return null; - } - - return formatMetadataResponse(metadataData, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/token/token-mint-burn-provider.ts b/packages/plugin-birdeye/src/providers/token/token-mint-burn-provider.ts deleted file mode 100644 index e60e8d7f9b..0000000000 --- a/packages/plugin-birdeye/src/providers/token/token-mint-burn-provider.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { - BASE_URL, - Chain, - extractChain, - extractContractAddresses, - formatValue, - makeApiRequest, - shortenAddress, -} from "../utils"; - -// Types -interface MintBurnEvent { - type: "mint" | "burn"; - amount: number; - amountUSD: number; - timestamp: number; - txHash: string; - address: string; -} - -interface MintBurnResponse { - events: MintBurnEvent[]; - token: string; -} - -// Constants -const MINT_BURN_KEYWORDS = [ - "mint", - "burn", - "minting", - "burning", - "token supply", - "supply changes", - "token burns", - "token mints", - "supply history", - "mint history", - "burn history", - "supply events", -] as const; - -// Helper functions -const containsMintBurnKeyword = (text: string): boolean => { - return MINT_BURN_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getTokenMintBurnHistory = async ( - apiKey: string, - contractAddress: string, - chain: Chain -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - }); - const url = `${BASE_URL}/token/mint_burn?${params.toString()}`; - - elizaLogger.info( - `Fetching mint/burn history for ${contractAddress} on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { apiKey, chain }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error( - "Error fetching mint/burn history:", - error.message - ); - } - return null; - } -}; - -const formatEventData = (event: MintBurnEvent): string => { - const date = new Date(event.timestamp * 1000).toLocaleString(); - const eventType = event.type === "mint" ? "šŸŸ¢ Mint" : "šŸ”“ Burn"; - - let response = `${eventType} Event - ${date}\n`; - response += ` ā€¢ Amount: ${formatValue(event.amount)}\n`; - response += ` ā€¢ Value: ${formatValue(event.amountUSD)}\n`; - response += ` ā€¢ By: ${shortenAddress(event.address)}\n`; - response += ` ā€¢ Tx: ${shortenAddress(event.txHash)}`; - return response; -}; - -const analyzeMintBurnTrends = (events: MintBurnEvent[]): string => { - const mints = events.filter((e) => e.type === "mint"); - const burns = events.filter((e) => e.type === "burn"); - - const totalMinted = mints.reduce((sum, e) => sum + e.amount, 0); - const totalBurned = burns.reduce((sum, e) => sum + e.amount, 0); - const netChange = totalMinted - totalBurned; - - let analysis = "šŸ“Š Supply Change Analysis\n\n"; - - // Supply change metrics - analysis += `Total Minted: ${formatValue(totalMinted)}\n`; - analysis += `Total Burned: ${formatValue(totalBurned)}\n`; - analysis += `Net Change: ${formatValue(Math.abs(netChange))} ${netChange >= 0 ? "increase" : "decrease"}\n\n`; - - // Activity analysis - analysis += "Recent Activity:\n"; - if (mints.length === 0 && burns.length === 0) { - analysis += "ā€¢ No mint/burn activity in the period\n"; - } else { - if (mints.length > 0) { - analysis += `ā€¢ ${mints.length} mint events\n`; - } - if (burns.length > 0) { - analysis += `ā€¢ ${burns.length} burn events\n`; - } - } - - // Supply trend - if (netChange > 0) { - analysis += "\nšŸ“ˆ Supply is expanding"; - } else if (netChange < 0) { - analysis += "\nšŸ“‰ Supply is contracting"; - } else { - analysis += "\nāž”ļø Supply is stable"; - } - - return analysis; -}; - -const formatMintBurnResponse = ( - data: MintBurnResponse, - chain: Chain -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - - let response = `Mint/Burn History for ${data.token} on ${chainName}\n\n`; - - if (data.events.length === 0) { - return response + "No mint/burn events found."; - } - - response += analyzeMintBurnTrends(data.events); - response += "\n\nšŸ“œ Recent Events\n"; - - // Sort events by timestamp in descending order - const sortedEvents = [...data.events].sort( - (a, b) => b.timestamp - a.timestamp - ); - sortedEvents.forEach((event) => { - response += "\n" + formatEventData(event) + "\n"; - }); - - return response; -}; - -export const tokenMintBurnProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsMintBurnKeyword(messageText)) { - return null; - } - - const addresses = extractContractAddresses(messageText); - if (addresses.length === 0) { - return null; - } - - const chain = extractChain(messageText); - - elizaLogger.info( - `TOKEN MINT/BURN provider activated for ${addresses[0]} on ${chain}` - ); - - const mintBurnData = await getTokenMintBurnHistory( - apiKey, - addresses[0], - chain - ); - - if (!mintBurnData) { - return null; - } - - return formatMintBurnResponse(mintBurnData, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/token/token-overview-provider.ts b/packages/plugin-birdeye/src/providers/token/token-overview-provider.ts deleted file mode 100644 index 594a72fe1e..0000000000 --- a/packages/plugin-birdeye/src/providers/token/token-overview-provider.ts +++ /dev/null @@ -1,266 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; - -// Types -interface TokenExtensions { - website?: string; - twitter?: string; - telegram?: string; - discord?: string; - description?: string; - coingeckoId?: string; -} - -interface TokenOverview { - // Basic token info - address: string; - symbol: string; - name: string; - decimals: number; - logoURI: string; - - // Price and market data - price: number; - priceChange24hPercent: number; - liquidity: number; - marketCap: number; - realMc: number; - - // Supply info - supply: number; - circulatingSupply: number; - holder: number; - - // Volume data - v24h: number; - v24hUSD: number; - - // Social/metadata - extensions?: TokenExtensions; - - // Trading info - lastTradeUnixTime: number; - numberMarkets: number; -} - -// Constants -const OVERVIEW_KEYWORDS = [ - "overview", - "details", - "info", - "information", - "about", - "tell me about", - "what is", - "show me", -] as const; - -const CHAIN_KEYWORDS = [ - "solana", - "ethereum", - "arbitrum", - "avalanche", - "bsc", - "optimism", - "polygon", - "base", - "zksync", - "sui", -] as const; - -const BASE_URL = "https://public-api.birdeye.so"; - -// Helper functions -const containsOverviewKeyword = (text: string): boolean => { - return OVERVIEW_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const extractChain = (text: string): string => { - const chain = CHAIN_KEYWORDS.find((chain) => - text.toLowerCase().includes(chain.toLowerCase()) - ); - return chain || "solana"; -}; - -const extractContractAddress = (text: string): string | null => { - const words = text.split(/\s+/); - - for (const word of words) { - // Ethereum-like addresses (0x...) - if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { - return word; - } - // Solana addresses (base58, typically 32-44 chars) - if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { - return word; - } - } - return null; -}; - -const formatNumber = (num: number): string => { - if (!num && num !== 0) return "N/A"; - return num.toLocaleString("en-US", { - minimumFractionDigits: 2, - maximumFractionDigits: 6, - }); -}; - -const formatSocialLinks = (extensions?: TokenExtensions): string => { - if (!extensions) return ""; - - return Object.entries(extensions) - .filter(([key, value]) => { - try { - return Boolean( - value && - typeof value === "string" && - ["website", "twitter", "telegram", "discord"].includes( - key - ) - ); - } catch (err) { - elizaLogger.warn( - `Error processing social link for key ${key}:`, - err - ); - return false; - } - }) - .map(([key, value]) => { - try { - return `ā€¢ ${key.charAt(0).toUpperCase() + key.slice(1)}: ${value}`; - } catch (err) { - elizaLogger.error( - `Error formatting social link for ${key}:`, - err - ); - return ""; - } - }) - .filter(Boolean) - .join("\n"); -}; - -const formatTokenOverview = (token: TokenOverview, chain: string): string => { - const lastTradeTime = new Date( - token.lastTradeUnixTime * 1000 - ).toLocaleString(); - const socialLinks = formatSocialLinks(token.extensions); - - return `Token Overview for ${token.name} (${token.symbol}) on ${chain.charAt(0).toUpperCase() + chain.slice(1)} - -šŸ“Š Market Data -ā€¢ Current Price: $${formatNumber(token.price)} -ā€¢ 24h Change: ${formatNumber(token.priceChange24hPercent)}% -ā€¢ Market Cap: $${formatNumber(token.marketCap)} -ā€¢ Real Market Cap: $${formatNumber(token.realMc)} -ā€¢ Liquidity: $${formatNumber(token.liquidity)} - -šŸ“ˆ Trading Info -ā€¢ 24h Volume: $${formatNumber(token.v24hUSD)} -ā€¢ Number of Markets: ${token.numberMarkets} -ā€¢ Last Trade: ${lastTradeTime} - -šŸ’° Supply Information -ā€¢ Total Supply: ${formatNumber(token.supply)} -ā€¢ Circulating Supply: ${formatNumber(token.circulatingSupply)} -ā€¢ Number of Holders: ${token.holder ? formatNumber(token.holder) : "N/A"} - -šŸ”— Token Details -ā€¢ Contract: ${token.address} -ā€¢ Decimals: ${token.decimals} -${token.extensions?.description ? `ā€¢ Description: ${token.extensions.description}\n` : ""} -${socialLinks ? `\nšŸŒ Social Links\n${socialLinks}` : ""}`; -}; - -const getTokenOverview = async ( - apiKey: string, - contractAddress: string, - chain: string = "solana" -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - }); - const url = `${BASE_URL}/defi/token_overview?${params.toString()}`; - - elizaLogger.info( - `Fetching token overview for address ${contractAddress} on ${chain} from:`, - url - ); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - "x-chain": chain, - }, - }); - - if (!response.ok) { - if (response.status === 404) { - elizaLogger.warn( - `Token not found: ${contractAddress} on ${chain}` - ); - return null; - } - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - return data.data; - } catch (error) { - elizaLogger.error("Error fetching token overview:", error); - return null; - } -}; - -export const tokenOverviewProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsOverviewKeyword(messageText)) { - return null; - } - - const contractAddress = extractContractAddress(messageText); - if (!contractAddress) { - return null; - } - - const chain = extractChain(messageText); - - elizaLogger.info( - `TOKEN OVERVIEW provider activated for address ${contractAddress} on ${chain}` - ); - - const tokenOverview = await getTokenOverview( - apiKey, - contractAddress, - chain - ); - - if (!tokenOverview) { - return null; - } - - return formatTokenOverview(tokenOverview, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/token/token-security-provider.ts b/packages/plugin-birdeye/src/providers/token/token-security-provider.ts deleted file mode 100644 index 1affa34ad4..0000000000 --- a/packages/plugin-birdeye/src/providers/token/token-security-provider.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { - BASE_URL, - Chain, - extractChain, - extractContractAddresses, - makeApiRequest, -} from "../utils"; - -// Types -interface SecurityData { - isHoneypot: boolean; - isProxy: boolean; - isVerified: boolean; - isAudited: boolean; - isRenounced: boolean; - isMintable: boolean; - isPausable: boolean; - hasBlacklist: boolean; - hasFeeOnTransfer: boolean; - transferFeePercentage: number; - riskLevel: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL"; - riskFactors: string[]; -} - -interface SecurityResponse { - data: SecurityData; - token: string; -} - -// Constants -const SECURITY_KEYWORDS = [ - "security", - "risk", - "audit", - "safety", - "honeypot", - "scam", - "safe", - "verified", - "contract security", - "token security", - "token safety", - "token risk", - "token audit", - "security check", - "risk check", - "safety check", -] as const; - -// Helper functions -const containsSecurityKeyword = (text: string): boolean => { - return SECURITY_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getTokenSecurity = async ( - apiKey: string, - contractAddress: string, - chain: Chain -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - }); - const url = `${BASE_URL}/token/security?${params.toString()}`; - - elizaLogger.info( - `Fetching security data for ${contractAddress} on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { apiKey, chain }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching security data:", error.message); - } - return null; - } -}; - -const getRiskEmoji = (riskLevel: SecurityData["riskLevel"]): string => { - switch (riskLevel) { - case "LOW": - return "āœ…"; - case "MEDIUM": - return "āš ļø"; - case "HIGH": - return "šŸšØ"; - case "CRITICAL": - return "šŸ’€"; - default: - return "ā“"; - } -}; - -const formatSecurityFeatures = (data: SecurityData): string => { - const features = [ - { name: "Contract Verified", value: data.isVerified }, - { name: "Contract Audited", value: data.isAudited }, - { name: "Ownership Renounced", value: data.isRenounced }, - { name: "Mintable Token", value: data.isMintable }, - { name: "Pausable Token", value: data.isPausable }, - { name: "Has Blacklist", value: data.hasBlacklist }, - { name: "Has Transfer Fee", value: data.hasFeeOnTransfer }, - ]; - - return features - .map(({ name, value }) => `ā€¢ ${name}: ${value ? "āœ…" : "āŒ"}`) - .join("\n"); -}; - -const analyzeSecurityRisks = (data: SecurityData): string => { - let analysis = ""; - - // Critical checks - if (data.isHoneypot) { - analysis += - "šŸš« CRITICAL: Token is identified as a honeypot! DO NOT TRADE.\n"; - } - - if (data.isProxy) { - analysis += - "āš ļø Contract is upgradeable (proxy). Owner can modify functionality.\n"; - } - - // Fee analysis - if (data.hasFeeOnTransfer) { - const feeLevel = data.transferFeePercentage > 5 ? "High" : "Standard"; - analysis += `šŸ’ø ${feeLevel} transfer fee (${data.transferFeePercentage}%).\n`; - } - - // Contract security - if (!data.isVerified) { - analysis += "āš ļø Contract is not verified. Cannot audit code.\n"; - } - if (!data.isAudited) { - analysis += "āš ļø No professional audit found.\n"; - } - if (!data.isRenounced) { - analysis += - "šŸ‘¤ Contract ownership retained. Owner can modify contract.\n"; - } - - // Token features - if (data.isMintable) { - analysis += "šŸ“ˆ Token supply can be increased by owner.\n"; - } - if (data.isPausable) { - analysis += "āøļø Trading can be paused by owner.\n"; - } - if (data.hasBlacklist) { - analysis += "šŸš« Addresses can be blacklisted from trading.\n"; - } - - // Risk factors - if (data.riskFactors.length > 0) { - analysis += "\nIdentified Risk Factors:\n"; - data.riskFactors.forEach((factor) => { - analysis += `ā€¢ ${factor}\n`; - }); - } - - return analysis; -}; - -const formatSecurityResponse = ( - data: SecurityResponse, - chain: Chain -): string => { - const { data: securityData } = data; - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - - let response = `Security Analysis for ${data.token} on ${chainName}\n\n`; - - // Overall Risk Level - response += `šŸŽÆ Risk Level: ${getRiskEmoji(securityData.riskLevel)} ${securityData.riskLevel}\n\n`; - - // Security Analysis - response += "šŸ” Security Analysis\n"; - response += analyzeSecurityRisks(securityData) + "\n\n"; - - // Contract Features - response += "šŸ“‹ Contract Features\n"; - response += formatSecurityFeatures(securityData); - - return response; -}; - -export const tokenSecurityProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsSecurityKeyword(messageText)) { - return null; - } - - const addresses = extractContractAddresses(messageText); - if (addresses.length === 0) { - return null; - } - - const chain = extractChain(messageText); - - elizaLogger.info( - `TOKEN SECURITY provider activated for ${addresses[0]} on ${chain}` - ); - - const securityData = await getTokenSecurity( - apiKey, - addresses[0], - chain - ); - - if (!securityData) { - return null; - } - - return formatSecurityResponse(securityData, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/token/token-trade-provider.ts b/packages/plugin-birdeye/src/providers/token/token-trade-provider.ts deleted file mode 100644 index 8c19da3f12..0000000000 --- a/packages/plugin-birdeye/src/providers/token/token-trade-provider.ts +++ /dev/null @@ -1,327 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; - -// Types -interface Trade { - timestamp: number; - price: number; - volume: number; - side: "buy" | "sell"; - source: string; - txHash: string; - buyer?: string; - seller?: string; -} - -interface TokenTradeData { - trades: Trade[]; - totalCount: number; - token: string; -} - -interface MultiTokenTradeData { - [tokenAddress: string]: TokenTradeData; -} - -// Constants -const TRADE_KEYWORDS = [ - "trades", - "trading", - "transactions", - "swaps", - "buys", - "sells", - "orders", - "executions", - "trade history", - "trading history", - "recent trades", -] as const; - -const CHAIN_KEYWORDS = [ - "solana", - "ethereum", - "arbitrum", - "avalanche", - "bsc", - "optimism", - "polygon", - "base", - "zksync", - "sui", -] as const; - -const BASE_URL = "https://public-api.birdeye.so"; - -// Helper functions -const containsTradeKeyword = (text: string): boolean => { - return TRADE_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const extractChain = (text: string): string => { - const chain = CHAIN_KEYWORDS.find((chain) => - text.toLowerCase().includes(chain.toLowerCase()) - ); - return chain || "solana"; -}; - -const extractContractAddresses = (text: string): string[] => { - const words = text.split(/\s+/); - const addresses: string[] = []; - - for (const word of words) { - // Ethereum-like addresses (0x...) - if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { - addresses.push(word); - } - // Solana addresses (base58, typically 32-44 chars) - if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { - addresses.push(word); - } - } - return addresses; -}; - -const getSingleTokenTrades = async ( - apiKey: string, - contractAddress: string, - chain: string = "solana", - limit: number = 10 -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - limit: limit.toString(), - }); - const url = `${BASE_URL}/token/trade_data_single?${params.toString()}`; - - elizaLogger.info( - `Fetching trade data for token ${contractAddress} on ${chain} from:`, - url - ); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - "x-chain": chain, - }, - }); - - if (!response.ok) { - if (response.status === 404) { - elizaLogger.warn( - `Token not found: ${contractAddress} on ${chain}` - ); - return null; - } - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - return data.data; - } catch (error) { - elizaLogger.error("Error fetching token trade data:", error); - return null; - } -}; - -const getMultipleTokenTrades = async ( - apiKey: string, - addresses: string[], - chain: string = "solana", - limit: number = 5 -): Promise => { - try { - const params = new URLSearchParams({ - addresses: addresses.join(","), - limit: limit.toString(), - }); - const url = `${BASE_URL}/token/trade_data_multiple?${params.toString()}`; - - elizaLogger.info( - `Fetching trade data for ${addresses.length} tokens on ${chain} from:`, - url - ); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - "x-chain": chain, - }, - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - return data.data; - } catch (error) { - elizaLogger.error("Error fetching multiple token trade data:", error); - return null; - } -}; - -const formatValue = (value: number): string => { - if (value >= 1_000_000_000) { - return `$${(value / 1_000_000_000).toFixed(2)}B`; - } - if (value >= 1_000_000) { - return `$${(value / 1_000_000).toFixed(2)}M`; - } - if (value >= 1_000) { - return `$${(value / 1_000).toFixed(2)}K`; - } - return `$${value.toFixed(2)}`; -}; - -const shortenAddress = (address: string): string => { - if (!address || address.length <= 12) return address || "Unknown"; - return `${address.slice(0, 6)}...${address.slice(-4)}`; -}; - -const formatTrade = (trade: Trade): string => { - const timestamp = new Date(trade.timestamp * 1000).toLocaleString(); - const priceFormatted = - trade.price != null - ? trade.price < 0.01 - ? trade.price.toExponential(2) - : trade.price.toFixed(2) - : "N/A"; - const side = trade.side === "buy" ? "šŸŸ¢ Buy" : "šŸ”“ Sell"; - - let response = `${side} - ${timestamp}\n`; - response += `ā€¢ Price: $${priceFormatted}\n`; - response += `ā€¢ Volume: ${trade.volume ? formatValue(trade.volume) : "N/A"}\n`; - response += `ā€¢ Source: ${trade.source || "Unknown"}\n`; - if (trade.buyer && trade.seller) { - response += `ā€¢ Buyer: ${shortenAddress(trade.buyer)}\n`; - response += `ā€¢ Seller: ${shortenAddress(trade.seller)}\n`; - } - response += `ā€¢ Tx: ${trade.txHash ? shortenAddress(trade.txHash) : "N/A"}`; - - return response; -}; - -const formatSingleTokenTradeResponse = ( - data: TokenTradeData, - chain: string -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - let response = `Recent Trades for ${data.token} on ${chainName}:\n\n`; - - if (data.trades.length === 0) { - return response + "No recent trades found."; - } - - data.trades.forEach((trade, index) => { - response += `${index + 1}. ${formatTrade(trade)}\n\n`; - }); - - if (data.totalCount > data.trades.length) { - response += `Showing ${data.trades.length} of ${data.totalCount} total trades.`; - } - - return response; -}; - -const formatMultipleTokenTradeResponse = ( - data: MultiTokenTradeData, - chain: string -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - let response = `Recent Trades on ${chainName}:\n\n`; - - const tokens = Object.entries(data); - if (tokens.length === 0) { - return response + "No trades found for any token."; - } - - tokens.forEach(([address, tokenData]) => { - response += `${tokenData.token} (${shortenAddress(address)}):\n`; - - if (tokenData.trades.length === 0) { - response += "No recent trades\n\n"; - return; - } - - tokenData.trades.forEach((trade, index) => { - response += `${index + 1}. ${formatTrade(trade)}\n`; - }); - - if (tokenData.totalCount > tokenData.trades.length) { - response += `Showing ${tokenData.trades.length} of ${tokenData.totalCount} trades\n`; - } - response += "\n"; - }); - - return response; -}; - -export const tokenTradeProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsTradeKeyword(messageText)) { - return null; - } - - const addresses = extractContractAddresses(messageText); - if (addresses.length === 0) { - return null; - } - - const chain = extractChain(messageText); - - if (addresses.length === 1) { - elizaLogger.info( - `TOKEN TRADE provider activated for address ${addresses[0]} on ${chain}` - ); - - const tradeData = await getSingleTokenTrades( - apiKey, - addresses[0], - chain - ); - - if (!tradeData) { - return null; - } - - return formatSingleTokenTradeResponse(tradeData, chain); - } else { - elizaLogger.info( - `MULTIPLE TOKEN TRADE provider activated for ${addresses.length} addresses on ${chain}` - ); - - const tradeData = await getMultipleTokenTrades( - apiKey, - addresses, - chain - ); - - if (!tradeData) { - return null; - } - - return formatMultipleTokenTradeResponse(tradeData, chain); - } - }, -}; diff --git a/packages/plugin-birdeye/src/providers/token/top-traders-provider.ts b/packages/plugin-birdeye/src/providers/token/top-traders-provider.ts deleted file mode 100644 index ec8c9824e8..0000000000 --- a/packages/plugin-birdeye/src/providers/token/top-traders-provider.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { BASE_URL, Chain, makeApiRequest } from "../utils"; - -// Types -interface Trader { - address: string; - tradeCount: number; - volume: number; - profit: number; - lastTradeTime: number; -} - -interface TopTradersResponse { - traders: Trader[]; -} - -// Constants -const TOP_TRADERS_KEYWORDS = [ - "top traders", - "best traders", - "leading traders", - "most successful traders", - "highest volume traders", -] as const; - -// Helper functions -const containsTopTradersKeyword = (text: string): boolean => { - return TOP_TRADERS_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getTopTraders = async ( - apiKey: string, - chain: Chain = "solana" -): Promise => { - try { - const url = `${BASE_URL}/token/top_traders`; - - elizaLogger.info("Fetching top traders from:", url); - - return await makeApiRequest(url, { apiKey, chain }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching top traders:", error.message); - } - return null; - } -}; - -const formatTopTradersResponse = (data: TopTradersResponse): string => { - let response = "šŸ† Top Traders\n\n"; - - data.traders.forEach((trader, index) => { - const lastTradeDate = new Date( - trader.lastTradeTime * 1000 - ).toLocaleString(); - const profitPrefix = trader.profit >= 0 ? "+" : ""; - - response += `${index + 1}. Trader ${trader.address.slice(0, 8)}...${trader.address.slice(-6)}\n`; - response += `ā€¢ Trade Count: ${trader.tradeCount.toLocaleString()}\n`; - response += `ā€¢ Volume: $${trader.volume.toLocaleString()}\n`; - response += `ā€¢ Profit: ${profitPrefix}$${trader.profit.toLocaleString()}\n`; - response += `ā€¢ Last Trade: ${lastTradeDate}\n\n`; - }); - - return response.trim(); -}; - -export const topTradersProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsTopTradersKeyword(messageText)) { - return null; - } - - elizaLogger.info("TOP_TRADERS provider activated"); - - const tradersData = await getTopTraders(apiKey); - - if (!tradersData) { - return null; - } - - return formatTopTradersResponse(tradersData); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/token/trending-tokens-provider.ts b/packages/plugin-birdeye/src/providers/token/trending-tokens-provider.ts deleted file mode 100644 index afe3134653..0000000000 --- a/packages/plugin-birdeye/src/providers/token/trending-tokens-provider.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; - -interface TrendingToken { - address: string; - name: string; - symbol: string; - decimals: number; - volume24hUSD: number; - liquidity: number; - logoURI: string; - price: number; -} - -const TRENDING_KEYWORDS = [ - "trending", - "popular", - "hot", - "top", - "performing", - "movers", - "gainers", - "volume", - "liquidity", - "market cap", - "price action", -]; - -const TOKEN_KEYWORDS = [ - "token", - "tokens", - "coin", - "coins", - "crypto", - "cryptocurrency", - "asset", - "assets", - "sol", - "solana", -]; - -const ASCENDING_KEYWORDS = [ - "lowest", - "worst", - "bottom", - "least", - "smallest", - "weakest", -]; - -const PAGINATION_KEYWORDS = [ - "more", - "additional", - "next", - "other", - "show more", - "continue", -]; - -const SUPPORTED_CHAINS = [ - "solana", - "ethereum", - "arbitrum", - "avalanche", - "bsc", - "optimism", - "polygon", - "base", - "zksync", - "sui", -]; - -const BASE_URL = "https://public-api.birdeye.so"; - -interface GetTrendingTokensOptions { - sort_by?: "volume24hUSD" | "rank" | "liquidity"; - sort_type?: "desc" | "asc"; - offset?: number; - limit?: number; - min_liquidity?: number; - chain?: string; -} - -const getTrendingTokens = async ( - apiKey: string, - options: GetTrendingTokensOptions = {} -): Promise => { - try { - const { - sort_by = "volume24hUSD", - sort_type = "desc", - offset = 0, - limit = 10, - min_liquidity = 1000, - chain = "solana", - } = options; - - const params = new URLSearchParams({ - sort_by, - sort_type, - offset: offset.toString(), - limit: limit.toString(), - min_liquidity: min_liquidity.toString(), - }); - - const url = `${BASE_URL}/defi/token_trending?${params.toString()}`; - elizaLogger.info("Fetching trending tokens from:", url); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - "x-chain": chain, - }, - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - return (await response.json()).data.tokens; - } catch (error) { - elizaLogger.error("Error fetching trending tokens:", error); - throw error; - } -}; - -const formatTrendingTokensToString = ( - tokens: TrendingToken[], - chain: string -): string => { - if (!tokens.length) { - return "No trending tokens found."; - } - - const formattedTokens = tokens - .map((token, index) => { - const priceFormatted = - token.price != null - ? token.price < 0.01 - ? token.price.toExponential(2) - : token.price.toFixed(2) - : "N/A"; - - const volume = - token.volume24hUSD != null - ? `$${(token.volume24hUSD / 1_000_000).toFixed(2)}M` - : "N/A"; - - const liquidity = - token.liquidity != null - ? `$${(token.liquidity / 1_000_000).toFixed(2)}M` - : "N/A"; - - return ( - `${index + 1}. ${token.name || "Unknown"} (${token.symbol || "N/A"}):\n` + - ` Price: ${priceFormatted}\n` + - ` Volume 24h: ${volume}\n` + - ` Liquidity: ${liquidity}` - ); - }) - .join("\n\n"); - - return `Here are the trending tokens on ${chain.charAt(0).toUpperCase() + chain.slice(1)}:\n\n${formattedTokens}`; -}; - -export const trendingTokensProvider: Provider = { - get: async (runtime: IAgentRuntime, message: Memory, state?: State) => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - return null; - } - - const messageText = message.content.text.toLowerCase(); - - // Check if message contains trending-related keywords - const hasTrendingKeyword = TRENDING_KEYWORDS.some((keyword) => - messageText.includes(keyword) - ); - - // Check if message contains token-related keywords - const hasTokenKeyword = TOKEN_KEYWORDS.some((keyword) => - messageText.includes(keyword) - ); - - // Check if the message is a direct question about trends - const isQuestionAboutTrends = - messageText.includes("?") && - (messageText.includes("what") || - messageText.includes("which") || - messageText.includes("show")) && - hasTrendingKeyword; - - // Check recent conversation context from state - const recentMessages = (state?.recentMessagesData || []) as Memory[]; - const isInTrendingConversation = recentMessages.some( - (msg) => - msg.content?.text?.toLowerCase().includes("trending") || - msg.content?.text?.toLowerCase().includes("token") - ); - - // Determine sorting direction based on keywords - const isAscending = ASCENDING_KEYWORDS.some((keyword) => - messageText.includes(keyword) - ); - const sortType = isAscending ? "asc" : "desc"; - - // Determine if this is a pagination request - const isPaginationRequest = PAGINATION_KEYWORDS.some((keyword) => - messageText.includes(keyword) - ); - - // Get the current offset from state or default to 0 - const currentOffset = (state?.trendingTokensOffset as number) || 0; - const offset = isPaginationRequest ? currentOffset + 10 : 0; - - // Determine sort criteria based on message content - let sortBy: "volume24hUSD" | "rank" | "liquidity" = "volume24hUSD"; - if (messageText.includes("liquidity")) { - sortBy = "liquidity"; - } else if (messageText.includes("rank")) { - sortBy = "rank"; - } - - // Determine which chain is being asked about - const requestedChain = - SUPPORTED_CHAINS.find((chain) => - messageText.includes(chain.toLowerCase()) - ) || "solana"; - - // Combine signals to make decision - const shouldProvideData = - // Direct questions about trends - isQuestionAboutTrends || - // Explicit mentions of trending tokens - (hasTrendingKeyword && hasTokenKeyword) || - // Follow-up in a trending conversation - (isInTrendingConversation && hasTokenKeyword) || - // Pagination request in conversation context - (isPaginationRequest && isInTrendingConversation); - - if (!shouldProvideData) { - return null; - } - - elizaLogger.info( - `TRENDING TOKENS provider activated for ${requestedChain} trending tokens query` - ); - - const trendingTokens = await getTrendingTokens(apiKey, { - sort_by: sortBy, - sort_type: sortType, - offset, - limit: 10, - min_liquidity: 1000, - chain: requestedChain, - }); - - const formattedTrending = formatTrendingTokensToString( - trendingTokens, - requestedChain - ); - - return formattedTrending; - }, -}; diff --git a/packages/plugin-birdeye/src/providers/trader/gainers-losers-provider.ts b/packages/plugin-birdeye/src/providers/trader/gainers-losers-provider.ts deleted file mode 100644 index c9998243b1..0000000000 --- a/packages/plugin-birdeye/src/providers/trader/gainers-losers-provider.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; - -// Types -interface TokenMarketData { - address: string; - symbol: string; - name: string; - price: number; - priceChange24h: number; - priceChange24hPercent: number; - volume24h: number; - marketCap: number; - liquidity: number; - logoURI?: string; -} - -interface GainersLosersData { - gainers: TokenMarketData[]; - losers: TokenMarketData[]; - timestamp: number; -} - -// Constants -const GAINERS_KEYWORDS = [ - "gainers", - "top gainers", - "best performing", - "biggest gains", - "movers up", - "green", - "pumping", - "rising", -] as const; - -const LOSERS_KEYWORDS = [ - "losers", - "top losers", - "worst performing", - "biggest losses", - "movers down", - "red", - "dumping", - "falling", -] as const; - -const CHAIN_KEYWORDS = [ - "solana", - "ethereum", - "arbitrum", - "avalanche", - "bsc", - "optimism", - "polygon", - "base", - "zksync", - "sui", -] as const; - -const BASE_URL = "https://public-api.birdeye.so"; - -// Helper functions -const containsGainersKeyword = (text: string): boolean => { - return GAINERS_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const containsLosersKeyword = (text: string): boolean => { - return LOSERS_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const extractChain = (text: string): string => { - const chain = CHAIN_KEYWORDS.find((chain) => - text.toLowerCase().includes(chain.toLowerCase()) - ); - return chain || "solana"; -}; - -const getGainersLosers = async ( - apiKey: string, - chain: string = "solana" -): Promise => { - try { - const params = new URLSearchParams({ - limit: "10", // Get top 10 gainers and losers - }); - const url = `${BASE_URL}/trader/gainers-losers?${params.toString()}`; - - elizaLogger.info(`Fetching gainers/losers on ${chain} from:`, url); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - "x-chain": chain, - }, - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - return data.data; - } catch (error) { - elizaLogger.error("Error fetching gainers/losers:", error); - return null; - } -}; - -const formatValue = (value: number): string => { - if (value >= 1_000_000_000) { - return `$${(value / 1_000_000_000).toFixed(2)}B`; - } - if (value >= 1_000_000) { - return `$${(value / 1_000_000).toFixed(2)}M`; - } - if (value >= 1_000) { - return `$${(value / 1_000).toFixed(2)}K`; - } - return `$${value.toFixed(2)}`; -}; - -const formatTokenData = (token: TokenMarketData): string => { - const priceFormatted = - token.price < 0.01 - ? token.price.toExponential(2) - : token.price.toFixed(2); - - return ( - `ā€¢ ${token.symbol} (${token.name})\n` + - ` Price: $${priceFormatted}\n` + - ` 24h Change: ${token.priceChange24hPercent.toFixed(2)}% (${formatValue(token.priceChange24h)})\n` + - ` Volume: ${formatValue(token.volume24h)}\n` + - ` Market Cap: ${formatValue(token.marketCap)}\n` + - ` Liquidity: ${formatValue(token.liquidity)}` - ); -}; - -const formatGainersLosersResponse = ( - data: GainersLosersData, - chain: string, - showGainers: boolean, - showLosers: boolean -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - let response = `Market Movers on ${chainName}\n`; - response += `Last Updated: ${new Date(data.timestamp * 1000).toLocaleString()}\n\n`; - - if (showGainers && Array.isArray(data.gainers) && data.gainers.length > 0) { - response += `šŸ“ˆ Top Gainers:\n`; - data.gainers.forEach((token, index) => { - response += `\n${index + 1}. ${formatTokenData(token)}\n`; - }); - } - - if (showLosers && Array.isArray(data.losers) && data.losers.length > 0) { - if ( - showGainers && - Array.isArray(data.gainers) && - data.gainers.length > 0 - ) - response += "\n"; - response += `šŸ“‰ Top Losers:\n`; - data.losers.forEach((token, index) => { - response += `\n${index + 1}. ${formatTokenData(token)}\n`; - }); - } - - if ( - (!data.gainers?.length && !data.losers?.length) || - (showGainers && !data.gainers?.length && !showLosers) || - (showLosers && !data.losers?.length && !showGainers) - ) { - response += "No market data available at this time."; - } - - return response; -}; - -export const gainersLosersProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - const showGainers = containsGainersKeyword(messageText); - const showLosers = containsLosersKeyword(messageText); - - // If neither gainers nor losers are specifically mentioned, show both - const showBoth = !showGainers && !showLosers; - - if (!showGainers && !showLosers && !showBoth) { - return null; - } - - const chain = extractChain(messageText); - - elizaLogger.info(`GAINERS/LOSERS provider activated for ${chain}`); - - const marketData = await getGainersLosers(apiKey, chain); - - if (!marketData) { - return null; - } - - return formatGainersLosersResponse( - marketData, - chain, - showGainers || showBoth, - showLosers || showBoth - ); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/trader/index.ts b/packages/plugin-birdeye/src/providers/trader/index.ts deleted file mode 100644 index b5889ef376..0000000000 --- a/packages/plugin-birdeye/src/providers/trader/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./gainers-losers-provider"; -export * from "./trades-seek-provider"; diff --git a/packages/plugin-birdeye/src/providers/trader/trades-seek-provider.ts b/packages/plugin-birdeye/src/providers/trader/trades-seek-provider.ts deleted file mode 100644 index 20db87c394..0000000000 --- a/packages/plugin-birdeye/src/providers/trader/trades-seek-provider.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; - -// Types -interface Trade { - timestamp: number; - token: string; - tokenAddress: string; - price: number; - volume: number; - side: "buy" | "sell"; - source: string; - txHash: string; - buyer?: string; - seller?: string; -} - -interface TradesResponse { - trades: Trade[]; - totalCount: number; -} - -// Constants -const TIME_SEEK_KEYWORDS = [ - "trades since", - "trades after", - "trades before", - "trades from", - "trades at", - "trading since", - "trading after", - "trading before", - "trading from", - "trading at", - "transactions since", - "transactions after", - "transactions before", - "transactions from", - "transactions at", -] as const; - -const TIME_UNITS = { - second: 1, - minute: 60, - hour: 3600, - day: 86400, - week: 604800, - month: 2592000, -} as const; - -const CHAIN_KEYWORDS = [ - "solana", - "ethereum", - "arbitrum", - "avalanche", - "bsc", - "optimism", - "polygon", - "base", - "zksync", - "sui", -] as const; - -const BASE_URL = "https://public-api.birdeye.so"; - -// Helper functions -const containsTimeSeekKeyword = (text: string): boolean => { - return TIME_SEEK_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const extractChain = (text: string): string => { - const chain = CHAIN_KEYWORDS.find((chain) => - text.toLowerCase().includes(chain.toLowerCase()) - ); - return chain || "solana"; -}; - -const extractTimeFromText = (text: string): number | null => { - // Try to find time expressions like "1 hour ago", "2 days ago", etc. - const timeRegex = /(\d+)\s*(second|minute|hour|day|week|month)s?\s*ago/i; - const match = text.match(timeRegex); - - if (match) { - const amount = parseInt(match[1]); - const unit = match[2].toLowerCase() as keyof typeof TIME_UNITS; - const now = Math.floor(Date.now() / 1000); - return now - amount * TIME_UNITS[unit]; - } - - // Try to find specific date/time - const dateMatch = text.match(/(\d{4}-\d{2}-\d{2}|\d{2}\/\d{2}\/\d{4})/); - if (dateMatch) { - const date = new Date(dateMatch[1]); - if (!isNaN(date.getTime())) { - return Math.floor(date.getTime() / 1000); - } - } - - return null; -}; - -const getTradesByTime = async ( - apiKey: string, - timestamp: number, - chain: string = "solana", - limit: number = 10 -): Promise => { - try { - const params = new URLSearchParams({ - timestamp: timestamp.toString(), - limit: limit.toString(), - }); - const url = `${BASE_URL}/trader/trades_seek_time?${params.toString()}`; - - elizaLogger.info( - `Fetching trades since ${new Date(timestamp * 1000).toLocaleString()} on ${chain} from:`, - url - ); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - "x-chain": chain, - }, - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - return data.data; - } catch (error) { - elizaLogger.error("Error fetching trades by time:", error); - return null; - } -}; - -const formatValue = (value: number): string => { - if (value >= 1_000_000_000) { - return `$${(value / 1_000_000_000).toFixed(2)}B`; - } - if (value >= 1_000_000) { - return `$${(value / 1_000_000).toFixed(2)}M`; - } - if (value >= 1_000) { - return `$${(value / 1_000).toFixed(2)}K`; - } - return `$${value.toFixed(2)}`; -}; - -const shortenAddress = (address: string): string => { - if (!address || address.length <= 12) return address || "Unknown"; - return `${address.slice(0, 6)}...${address.slice(-4)}`; -}; - -const formatTrade = (trade: Trade): string => { - const timestamp = new Date(trade.timestamp * 1000).toLocaleString(); - const priceFormatted = - trade.price < 0.01 - ? trade.price.toExponential(2) - : trade.price.toFixed(2); - const side = trade.side === "buy" ? "šŸŸ¢ Buy" : "šŸ”“ Sell"; - - let response = `${side} ${trade.token} - ${timestamp}\n`; - response += `ā€¢ Token: ${shortenAddress(trade.tokenAddress)}\n`; - response += `ā€¢ Price: $${priceFormatted}\n`; - response += `ā€¢ Volume: ${formatValue(trade.volume)}\n`; - response += `ā€¢ Source: ${trade.source}\n`; - if (trade.buyer && trade.seller) { - response += `ā€¢ Buyer: ${shortenAddress(trade.buyer)}\n`; - response += `ā€¢ Seller: ${shortenAddress(trade.seller)}\n`; - } - response += `ā€¢ Tx: ${shortenAddress(trade.txHash)}`; - - return response; -}; - -const formatTradesResponse = ( - data: TradesResponse, - timestamp: number, - chain: string -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - const fromTime = new Date(timestamp * 1000).toLocaleString(); - let response = `Trades on ${chainName} since ${fromTime}:\n\n`; - - if (data.trades.length === 0) { - return response + "No trades found in this time period."; - } - - data.trades.forEach((trade, index) => { - response += `${index + 1}. ${formatTrade(trade)}\n\n`; - }); - - if (data.totalCount > data.trades.length) { - response += `Showing ${data.trades.length} of ${data.totalCount} total trades.`; - } - - return response; -}; - -export const tradesSeekProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsTimeSeekKeyword(messageText)) { - return null; - } - - const timestamp = extractTimeFromText(messageText); - if (!timestamp) { - return null; - } - - const chain = extractChain(messageText); - - elizaLogger.info( - `TRADES SEEK provider activated for time ${new Date(timestamp * 1000).toLocaleString()} on ${chain}` - ); - - const tradesData = await getTradesByTime(apiKey, timestamp, chain); - - if (!tradesData) { - return null; - } - - return formatTradesResponse(tradesData, timestamp, chain); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/utils.ts b/packages/plugin-birdeye/src/providers/utils.ts deleted file mode 100644 index 40db78f7a7..0000000000 --- a/packages/plugin-birdeye/src/providers/utils.ts +++ /dev/null @@ -1,303 +0,0 @@ -import { elizaLogger } from "@elizaos/core"; - -// Constants -export const BASE_URL = "https://public-api.birdeye.so"; - -export const CHAIN_KEYWORDS = [ - "solana", - "ethereum", - "arbitrum", - "avalanche", - "bsc", - "optimism", - "polygon", - "base", - "zksync", - "sui", -] as const; - -// Types -export type Chain = (typeof CHAIN_KEYWORDS)[number]; - -export class BirdeyeApiError extends Error { - constructor( - public status: number, - message: string - ) { - super(message); - this.name = "BirdeyeApiError"; - } -} - -export interface ApiResponse { - success: boolean; - data: T; - error?: string; -} - -// Time-related types and constants -export const TIME_UNITS = { - second: 1, - minute: 60, - hour: 3600, - day: 86400, - week: 604800, - month: 2592000, -} as const; - -export const TIMEFRAME_KEYWORDS = { - "1m": 60, - "3m": 180, - "5m": 300, - "15m": 900, - "30m": 1800, - "1h": 3600, - "2h": 7200, - "4h": 14400, - "6h": 21600, - "12h": 43200, - "1d": 86400, - "1w": 604800, -} as const; - -export type TimeUnit = keyof typeof TIME_UNITS; -export type Timeframe = keyof typeof TIMEFRAME_KEYWORDS; - -// Helper functions -export const extractChain = (text: string): Chain => { - const chain = CHAIN_KEYWORDS.find((chain) => - text.toLowerCase().includes(chain.toLowerCase()) - ); - return (chain || "solana") as Chain; -}; - -export const extractContractAddresses = (text: string): string[] => { - const words = text.split(/\s+/); - const addresses: string[] = []; - - for (const word of words) { - // Ethereum-like addresses (0x...) - for Ethereum, Arbitrum, Avalanche, BSC, Optimism, Polygon, Base, zkSync - if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { - addresses.push(word); - } - // Solana addresses (base58, typically 32-44 chars) - else if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { - addresses.push(word); - } - // Sui addresses - both formats: - // 1. Simple object ID: 0x followed by 64 hex chars - // 2. Full token format: 0x:::: - else if ( - /^0x[a-fA-F0-9]{64}$/i.test(word) || - /^0x[a-fA-F0-9]{64}::[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$/i.test(word) - ) { - addresses.push(word); - } - } - return addresses; -}; - -// Time extraction and analysis -export const extractTimeframe = (text: string): Timeframe => { - // First, check for explicit timeframe mentions - const timeframe = Object.keys(TIMEFRAME_KEYWORDS).find((tf) => - text.toLowerCase().includes(tf.toLowerCase()) - ); - if (timeframe) return timeframe as Timeframe; - - // Check for semantic timeframe hints - const semanticMap = { - "short term": "15m", - "medium term": "1h", - "long term": "1d", - intraday: "1h", - daily: "1d", - weekly: "1w", - detailed: "5m", - quick: "15m", - overview: "1d", - } as const; - - for (const [hint, tf] of Object.entries(semanticMap)) { - if (text.toLowerCase().includes(hint)) { - return tf as Timeframe; - } - } - - // Analyze for time-related words - if (text.match(/minute|min|minutes/i)) return "15m"; - if (text.match(/hour|hourly|hours/i)) return "1h"; - if (text.match(/day|daily|24h/i)) return "1d"; - if (text.match(/week|weekly/i)) return "1w"; - - // Default based on context - if (text.match(/trade|trades|trading|recent/i)) return "15m"; - if (text.match(/trend|analysis|analyze/i)) return "1h"; - if (text.match(/history|historical|long|performance/i)) return "1d"; - - return "1h"; // Default timeframe -}; - -export const extractTimeRange = ( - text: string -): { start: number; end: number } => { - const now = Math.floor(Date.now() / 1000); - - // Check for specific date ranges - const dateRangeMatch = text.match( - /from\s+(\d{4}-\d{2}-\d{2})\s+to\s+(\d{4}-\d{2}-\d{2})/i - ); - if (dateRangeMatch) { - const start = new Date(dateRangeMatch[1]).getTime() / 1000; - const end = new Date(dateRangeMatch[2]).getTime() / 1000; - return { start, end }; - } - - // Check for relative time expressions - const timeRegex = /(\d+)\s*(second|minute|hour|day|week|month)s?\s*ago/i; - const match = text.match(timeRegex); - if (match) { - const amount = parseInt(match[1]); - const unit = match[2].toLowerCase() as TimeUnit; - const start = now - amount * TIME_UNITS[unit]; - return { start, end: now }; - } - - // Check for semantic time ranges - const semanticRanges: Record = { - today: TIME_UNITS.day, - "this week": TIME_UNITS.week, - "this month": TIME_UNITS.month, - recent: TIME_UNITS.hour * 4, - latest: TIME_UNITS.hour, - "last hour": TIME_UNITS.hour, - "last day": TIME_UNITS.day, - "last week": TIME_UNITS.week, - "last month": TIME_UNITS.month, - }; - - for (const [range, duration] of Object.entries(semanticRanges)) { - if (text.toLowerCase().includes(range)) { - return { start: now - duration, end: now }; - } - } - - // Analyze context for appropriate default range - if (text.match(/trend|analysis|performance/i)) { - return { start: now - TIME_UNITS.week, end: now }; // 1 week for analysis - } - if (text.match(/trade|trades|trading|recent/i)) { - return { start: now - TIME_UNITS.day, end: now }; // 1 day for trading - } - if (text.match(/history|historical|long term/i)) { - return { start: now - TIME_UNITS.month, end: now }; // 1 month for history - } - - // Default to last 24 hours - return { start: now - TIME_UNITS.day, end: now }; -}; - -export const extractLimit = (text: string): number => { - // Check for explicit limit mentions - const limitMatch = text.match( - /\b(show|display|get|fetch|limit)\s+(\d+)\b/i - ); - if (limitMatch) { - const limit = parseInt(limitMatch[2]); - return Math.min(Math.max(limit, 1), 100); // Clamp between 1 and 100 - } - - // Check for semantic limit hints - if (text.match(/\b(all|everything|full|complete)\b/i)) return 100; - if (text.match(/\b(brief|quick|summary|overview)\b/i)) return 5; - if (text.match(/\b(detailed|comprehensive)\b/i)) return 50; - - // Default based on context - if (text.match(/\b(trade|trades|trading)\b/i)) return 10; - if (text.match(/\b(analysis|analyze|trend)\b/i)) return 24; - if (text.match(/\b(history|historical)\b/i)) return 50; - - return 10; // Default limit -}; - -// Formatting helpers -export const formatValue = (value: number): string => { - if (value >= 1_000_000_000) { - return `$${(value / 1_000_000_000).toFixed(2)}B`; - } - if (value >= 1_000_000) { - return `$${(value / 1_000_000).toFixed(2)}M`; - } - if (value >= 1_000) { - return `$${(value / 1_000).toFixed(2)}K`; - } - return `$${value.toFixed(2)}`; -}; - -export const formatPercentChange = (change?: number): string => { - if (change === undefined) return "N/A"; - const symbol = change >= 0 ? "šŸ“ˆ" : "šŸ“‰"; - return `${symbol} ${Math.abs(change).toFixed(2)}%`; -}; - -export const shortenAddress = (address: string): string => { - if (!address || address.length <= 12) return address || "Unknown"; - return `${address.slice(0, 6)}...${address.slice(-4)}`; -}; - -export const formatTimestamp = (timestamp: number): string => { - return new Date(timestamp * 1000).toLocaleString(); -}; - -export const formatPrice = (price: number): string => { - return price < 0.01 ? price.toExponential(2) : price.toFixed(2); -}; - -// API helpers -export async function makeApiRequest( - url: string, - options: { - apiKey: string; - chain?: Chain; - method?: "GET" | "POST"; - body?: any; - } -): Promise { - const { apiKey, chain = "solana", method = "GET", body } = options; - - try { - const response = await fetch(url, { - method, - headers: { - "X-API-KEY": apiKey, - "x-chain": chain, - ...(body && { "Content-Type": "application/json" }), - }, - ...(body && { body: JSON.stringify(body) }), - }); - - if (!response.ok) { - if (response.status === 404) { - throw new BirdeyeApiError(404, "Resource not found"); - } - if (response.status === 429) { - throw new BirdeyeApiError(429, "Rate limit exceeded"); - } - throw new BirdeyeApiError( - response.status, - `HTTP error! status: ${response.status}` - ); - } - - const responseJson: T = await response.json(); - - return responseJson; - } catch (error) { - if (error instanceof BirdeyeApiError) { - elizaLogger.error(`API Error (${error.status}):`, error.message); - } else { - elizaLogger.error("Error making API request:", error); - } - throw error; - } -} diff --git a/packages/plugin-birdeye/src/providers/wallet/index.ts b/packages/plugin-birdeye/src/providers/wallet/index.ts deleted file mode 100644 index 7cc0f1aa25..0000000000 --- a/packages/plugin-birdeye/src/providers/wallet/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from "./portfolio-multichain-provider"; -export * from "./supported-networks-provider"; -export * from "./token-balance-provider"; -export * from "./transaction-history-multichain-provider"; -export * from "./transaction-history-provider"; -export * from "./wallet-portfolio-provider"; diff --git a/packages/plugin-birdeye/src/providers/wallet/portfolio-multichain-provider.ts b/packages/plugin-birdeye/src/providers/wallet/portfolio-multichain-provider.ts deleted file mode 100644 index b9058ac7e3..0000000000 --- a/packages/plugin-birdeye/src/providers/wallet/portfolio-multichain-provider.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { BASE_URL, Chain, makeApiRequest } from "../utils"; - -// Types -interface TokenHolding { - chain: Chain; - tokenAddress: string; - symbol: string; - name: string; - balance: number; - price: number; - value: number; - priceChange24h: number; -} - -interface MultichainPortfolioResponse { - holdings: TokenHolding[]; - totalValue: number; - valueChange24h: number; -} - -// Constants -const MULTICHAIN_PORTFOLIO_KEYWORDS = [ - "multichain portfolio", - "cross chain portfolio", - "all chain portfolio", - "portfolio across chains", - "portfolio on all chains", -] as const; - -// Helper functions -const containsMultichainPortfolioKeyword = (text: string): boolean => { - return MULTICHAIN_PORTFOLIO_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const extractWalletAddress = (text: string): string | null => { - // Look for wallet address patterns - const addressMatch = text.match(/\b[1-9A-HJ-NP-Za-km-z]{32,44}\b/); - return addressMatch ? addressMatch[0] : null; -}; - -const getMultichainPortfolio = async ( - apiKey: string, - walletAddress: string -): Promise => { - try { - const url = `${BASE_URL}/wallet/portfolio_multichain`; - - elizaLogger.info("Fetching multichain portfolio from:", url); - - return await makeApiRequest(url, { - apiKey, - chain: "solana", - body: { wallet: walletAddress }, - }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error( - "Error fetching multichain portfolio:", - error.message - ); - } - return null; - } -}; - -const formatMultichainPortfolioResponse = ( - data: MultichainPortfolioResponse -): string => { - let response = "šŸŒ Multichain Portfolio Overview\n\n"; - - // Add total portfolio value and 24h change - const valueChangePercent = (data.valueChange24h * 100).toFixed(2); - const valueChangeEmoji = data.valueChange24h >= 0 ? "šŸ“ˆ" : "šŸ“‰"; - - response += `Total Portfolio Value: $${data.totalValue.toLocaleString()}\n`; - response += `24h Change: ${valueChangePercent}% ${valueChangeEmoji}\n\n`; - - // Group holdings by chain - const holdingsByChain = data.holdings.reduce( - (acc, holding) => { - if (!acc[holding.chain]) { - acc[holding.chain] = []; - } - acc[holding.chain].push(holding); - return acc; - }, - {} as Record - ); - - // Format holdings by chain - Object.entries(holdingsByChain).forEach(([chain, holdings]) => { - response += `${chain.toUpperCase()} Holdings\n`; - - // Sort holdings by value - holdings.sort((a, b) => b.value - a.value); - - holdings.forEach((holding) => { - const priceChangePercent = (holding.priceChange24h * 100).toFixed( - 2 - ); - const priceChangeEmoji = holding.priceChange24h >= 0 ? "šŸ“ˆ" : "šŸ“‰"; - - response += `ā€¢ ${holding.name} (${holding.symbol})\n`; - response += ` - Balance: ${holding.balance.toLocaleString()}\n`; - response += ` - Price: $${holding.price.toFixed(6)}\n`; - response += ` - Value: $${holding.value.toLocaleString()}\n`; - response += ` - 24h Change: ${priceChangePercent}% ${priceChangeEmoji}\n\n`; - }); - }); - - return response.trim(); -}; - -export const portfolioMultichainProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsMultichainPortfolioKeyword(messageText)) { - return null; - } - - const walletAddress = extractWalletAddress(messageText); - if (!walletAddress) { - return "Please provide a valid wallet address to check the multichain portfolio."; - } - - elizaLogger.info("PORTFOLIO_MULTICHAIN provider activated"); - - const portfolioData = await getMultichainPortfolio( - apiKey, - walletAddress - ); - - if (!portfolioData) { - return null; - } - - return formatMultichainPortfolioResponse(portfolioData); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/wallet/supported-networks-provider.ts b/packages/plugin-birdeye/src/providers/wallet/supported-networks-provider.ts deleted file mode 100644 index 26b6ca6bbb..0000000000 --- a/packages/plugin-birdeye/src/providers/wallet/supported-networks-provider.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { BASE_URL, Chain, makeApiRequest } from "../utils"; - -// Types -interface NetworkSupport { - chain: Chain; - status: "active" | "maintenance" | "deprecated"; - features: string[]; -} - -interface SupportedNetworksResponse { - networks: NetworkSupport[]; -} - -// Constants -const SUPPORTED_NETWORKS_KEYWORDS = [ - "supported wallet networks", - "wallet networks", - "wallet chains", - "supported wallet chains", - "wallet network support", -] as const; - -// Helper functions -const containsSupportedNetworksKeyword = (text: string): boolean => { - return SUPPORTED_NETWORKS_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getSupportedNetworks = async ( - apiKey: string -): Promise => { - try { - const url = `${BASE_URL}/wallet/supported_networks`; - - elizaLogger.info("Fetching supported wallet networks from:", url); - - return await makeApiRequest(url, { - apiKey, - chain: "solana", - }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error( - "Error fetching supported networks:", - error.message - ); - } - return null; - } -}; - -const formatSupportedNetworksResponse = ( - data: SupportedNetworksResponse -): string => { - let response = "šŸŒ Supported Wallet Networks\n\n"; - - // Group networks by status - const activeNetworks = data.networks.filter((n) => n.status === "active"); - const maintenanceNetworks = data.networks.filter( - (n) => n.status === "maintenance" - ); - const deprecatedNetworks = data.networks.filter( - (n) => n.status === "deprecated" - ); - - // Format active networks - if (activeNetworks.length > 0) { - response += "šŸŸ¢ Active Networks\n"; - activeNetworks.forEach((network) => { - response += `ā€¢ ${network.chain}\n`; - response += ` - Features: ${network.features.join(", ")}\n\n`; - }); - } - - // Format maintenance networks - if (maintenanceNetworks.length > 0) { - response += "šŸŸ” Networks Under Maintenance\n"; - maintenanceNetworks.forEach((network) => { - response += `ā€¢ ${network.chain}\n`; - response += ` - Features: ${network.features.join(", ")}\n\n`; - }); - } - - // Format deprecated networks - if (deprecatedNetworks.length > 0) { - response += "šŸ”“ Deprecated Networks\n"; - deprecatedNetworks.forEach((network) => { - response += `ā€¢ ${network.chain}\n\n`; - }); - } - - return response.trim(); -}; - -export const supportedNetworksProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsSupportedNetworksKeyword(messageText)) { - return null; - } - - elizaLogger.info("SUPPORTED_NETWORKS provider activated"); - - const networksData = await getSupportedNetworks(apiKey); - - if (!networksData) { - return null; - } - - return formatSupportedNetworksResponse(networksData); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/wallet/token-balance-provider.ts b/packages/plugin-birdeye/src/providers/wallet/token-balance-provider.ts deleted file mode 100644 index 6e82646c45..0000000000 --- a/packages/plugin-birdeye/src/providers/wallet/token-balance-provider.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { BASE_URL, Chain, makeApiRequest } from "../utils"; - -// Types -interface TokenBalance { - tokenAddress: string; - symbol: string; - name: string; - balance: number; - decimals: number; - price: number; - value: number; - priceChange24h: number; -} - -interface TokenBalanceResponse { - balances: TokenBalance[]; - totalValue: number; - valueChange24h: number; -} - -// Constants -const TOKEN_BALANCE_KEYWORDS = [ - "token balance", - "token holdings", - "wallet balance", - "wallet holdings", - "check balance", - "check holdings", -] as const; - -// Helper functions -const containsTokenBalanceKeyword = (text: string): boolean => { - return TOKEN_BALANCE_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const extractWalletAddress = (text: string): string | null => { - // Look for wallet address patterns - const addressMatch = text.match(/\b[1-9A-HJ-NP-Za-km-z]{32,44}\b/); - return addressMatch ? addressMatch[0] : null; -}; - -const getTokenBalance = async ( - apiKey: string, - walletAddress: string, - chain: Chain = "solana" -): Promise => { - try { - const url = `${BASE_URL}/wallet/token_balance`; - - elizaLogger.info("Fetching token balance from:", url); - - return await makeApiRequest(url, { - apiKey, - chain, - body: { wallet: walletAddress }, - }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching token balance:", error.message); - } - return null; - } -}; - -const formatTokenBalanceResponse = (data: TokenBalanceResponse): string => { - let response = "šŸ’° Token Balance Overview\n\n"; - - // Add total value and 24h change - const valueChangePercent = (data.valueChange24h * 100).toFixed(2); - const valueChangeEmoji = data.valueChange24h >= 0 ? "šŸ“ˆ" : "šŸ“‰"; - - response += `Total Value: $${data.totalValue.toLocaleString()}\n`; - response += `24h Change: ${valueChangePercent}% ${valueChangeEmoji}\n\n`; - - // Sort balances by value - const sortedBalances = [...data.balances].sort((a, b) => b.value - a.value); - - // Format individual token balances - sortedBalances.forEach((balance) => { - const priceChangePercent = (balance.priceChange24h * 100).toFixed(2); - const priceChangeEmoji = balance.priceChange24h >= 0 ? "šŸ“ˆ" : "šŸ“‰"; - - response += `${balance.name} (${balance.symbol})\n`; - response += `ā€¢ Balance: ${balance.balance.toLocaleString()}\n`; - response += `ā€¢ Price: $${balance.price.toFixed(6)}\n`; - response += `ā€¢ Value: $${balance.value.toLocaleString()}\n`; - response += `ā€¢ 24h Change: ${priceChangePercent}% ${priceChangeEmoji}\n\n`; - }); - - return response.trim(); -}; - -export const tokenBalanceProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsTokenBalanceKeyword(messageText)) { - return null; - } - - const walletAddress = extractWalletAddress(messageText); - if (!walletAddress) { - return "Please provide a valid wallet address to check the token balance."; - } - - elizaLogger.info("TOKEN_BALANCE provider activated"); - - const balanceData = await getTokenBalance(apiKey, walletAddress); - - if (!balanceData) { - return null; - } - - return formatTokenBalanceResponse(balanceData); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/wallet/transaction-history-multichain-provider.ts b/packages/plugin-birdeye/src/providers/wallet/transaction-history-multichain-provider.ts deleted file mode 100644 index 66a6c8cb3f..0000000000 --- a/packages/plugin-birdeye/src/providers/wallet/transaction-history-multichain-provider.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; -import { BASE_URL, Chain, makeApiRequest } from "../utils"; - -// Types -interface Transaction { - chain: Chain; - hash: string; - timestamp: number; - type: string; - status: "success" | "failed" | "pending"; - value: number; - fee: number; - from: string; - to: string; - tokenTransfers?: { - token: string; - amount: number; - value: number; - }[]; -} - -interface TransactionHistoryResponse { - transactions: Transaction[]; -} - -// Constants -const MULTICHAIN_HISTORY_KEYWORDS = [ - "multichain transactions", - "cross chain transactions", - "all chain transactions", - "transactions across chains", - "transaction history all chains", -] as const; - -// Helper functions -const containsMultichainHistoryKeyword = (text: string): boolean => { - return MULTICHAIN_HISTORY_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const extractWalletAddress = (text: string): string | null => { - // Look for wallet address patterns - const addressMatch = text.match(/\b[1-9A-HJ-NP-Za-km-z]{32,44}\b/); - return addressMatch ? addressMatch[0] : null; -}; - -const getTransactionHistory = async ( - apiKey: string, - walletAddress: string -): Promise => { - try { - const url = `${BASE_URL}/wallet/transaction_history_multichain`; - - elizaLogger.info("Fetching multichain transaction history from:", url); - - return await makeApiRequest(url, { - apiKey, - chain: "solana", - body: { wallet: walletAddress }, - }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error( - "Error fetching transaction history:", - error.message - ); - } - return null; - } -}; - -const formatTransactionStatus = (status: Transaction["status"]): string => { - switch (status) { - case "success": - return "āœ…"; - case "failed": - return "āŒ"; - case "pending": - return "ā³"; - default: - return "ā“"; - } -}; - -const formatTransactionHistoryResponse = ( - data: TransactionHistoryResponse -): string => { - let response = "šŸ“œ Multichain Transaction History\n\n"; - - // Group transactions by chain - const txsByChain = data.transactions.reduce( - (acc, tx) => { - if (!acc[tx.chain]) { - acc[tx.chain] = []; - } - acc[tx.chain].push(tx); - return acc; - }, - {} as Record - ); - - // Format transactions by chain - Object.entries(txsByChain).forEach(([chain, transactions]) => { - response += `${chain.toUpperCase()} Transactions\n`; - - // Sort transactions by timestamp (newest first) - transactions.sort((a, b) => b.timestamp - a.timestamp); - - transactions.forEach((tx) => { - const date = new Date(tx.timestamp * 1000).toLocaleString(); - const statusEmoji = formatTransactionStatus(tx.status); - - response += `${statusEmoji} ${tx.type} - ${date}\n`; - response += `ā€¢ Hash: ${tx.hash}\n`; - response += `ā€¢ Value: $${tx.value.toLocaleString()}\n`; - response += `ā€¢ Fee: $${tx.fee.toFixed(6)}\n`; - response += `ā€¢ From: ${tx.from}\n`; - response += `ā€¢ To: ${tx.to}\n`; - - if (tx.tokenTransfers && tx.tokenTransfers.length > 0) { - response += "ā€¢ Token Transfers:\n"; - tx.tokenTransfers.forEach((transfer) => { - response += ` - ${transfer.token}: ${transfer.amount} ($${transfer.value.toLocaleString()})\n`; - }); - } - - response += "\n"; - }); - }); - - return response.trim(); -}; - -export const transactionHistoryMultichainProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsMultichainHistoryKeyword(messageText)) { - return null; - } - - const walletAddress = extractWalletAddress(messageText); - if (!walletAddress) { - return "Please provide a valid wallet address to check the transaction history."; - } - - elizaLogger.info("TRANSACTION_HISTORY_MULTICHAIN provider activated"); - - const historyData = await getTransactionHistory(apiKey, walletAddress); - - if (!historyData) { - return null; - } - - return formatTransactionHistoryResponse(historyData); - }, -}; diff --git a/packages/plugin-birdeye/src/providers/wallet/transaction-history-provider.ts b/packages/plugin-birdeye/src/providers/wallet/transaction-history-provider.ts deleted file mode 100644 index a303e0fa10..0000000000 --- a/packages/plugin-birdeye/src/providers/wallet/transaction-history-provider.ts +++ /dev/null @@ -1,381 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; - -// Types -interface TokenTransfer { - token: string; - symbol: string; - amount: number; - value: number; - decimals: number; -} - -interface Transaction { - hash: string; - timestamp: number; - type: "send" | "receive" | "swap" | "mint" | "burn" | "other"; - from: string; - to: string; - value: number; - fee: number; - success: boolean; - transfers: TokenTransfer[]; -} - -interface TransactionHistory { - transactions: Transaction[]; - totalCount: number; -} - -interface MultichainTransactionHistory { - [chain: string]: TransactionHistory; -} - -// Constants -const TRANSACTION_KEYWORDS = [ - "transaction", - "transactions", - "history", - "transfers", - "activity", - "trades", - "swaps", - "sent", - "received", - "tx", - "txs", -] as const; - -const MULTICHAIN_KEYWORDS = [ - "all chains", - "multichain", - "multi-chain", - "cross chain", - "cross-chain", - "every chain", - "all networks", -] as const; - -const CHAIN_KEYWORDS = [ - "solana", - "ethereum", - "arbitrum", - "avalanche", - "bsc", - "optimism", - "polygon", - "base", - "zksync", - "sui", -] as const; - -const BASE_URL = "https://public-api.birdeye.so"; - -// Helper functions -const containsTransactionKeyword = (text: string): boolean => { - return TRANSACTION_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const isMultichainRequest = (text: string): boolean => { - return MULTICHAIN_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const extractChain = (text: string): string => { - const chain = CHAIN_KEYWORDS.find((chain) => - text.toLowerCase().includes(chain.toLowerCase()) - ); - return chain || "solana"; -}; - -const extractWalletAddress = (text: string): string | null => { - const words = text.split(/\s+/); - - for (const word of words) { - // Ethereum-like addresses (0x...) - if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { - return word; - } - // Solana addresses (base58, typically 32-44 chars) - if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { - return word; - } - } - return null; -}; - -const getTransactionHistory = async ( - apiKey: string, - walletAddress: string, - chain: string = "solana", - limit: number = 10 -): Promise => { - try { - const params = new URLSearchParams({ - wallet: walletAddress, - limit: limit.toString(), - }); - const url = `${BASE_URL}/wallet/transaction_history?${params.toString()}`; - - elizaLogger.info( - `Fetching transaction history for wallet ${walletAddress} on ${chain} from:`, - url - ); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - "x-chain": chain, - }, - }); - - if (!response.ok) { - if (response.status === 404) { - elizaLogger.warn( - `Wallet not found: ${walletAddress} on ${chain}` - ); - return null; - } - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - return data.data; - } catch (error) { - elizaLogger.error("Error fetching transaction history:", error); - return null; - } -}; - -const getMultichainTransactionHistory = async ( - apiKey: string, - walletAddress: string, - limit: number = 10 -): Promise => { - try { - const params = new URLSearchParams({ - wallet: walletAddress, - limit: limit.toString(), - }); - const url = `${BASE_URL}/wallet/transaction_history_multichain?${params.toString()}`; - - elizaLogger.info( - `Fetching multichain transaction history for wallet ${walletAddress} from:`, - url - ); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - }, - }); - - if (!response.ok) { - if (response.status === 404) { - elizaLogger.warn(`Wallet not found: ${walletAddress}`); - return null; - } - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - return data.data; - } catch (error) { - elizaLogger.error( - "Error fetching multichain transaction history:", - error - ); - return null; - } -}; - -const formatValue = (value: number): string => { - if (value >= 1_000_000_000) { - return `$${(value / 1_000_000_000).toFixed(2)}B`; - } - if (value >= 1_000_000) { - return `$${(value / 1_000_000).toFixed(2)}M`; - } - if (value >= 1_000) { - return `$${(value / 1_000).toFixed(2)}K`; - } - return `$${value.toFixed(2)}`; -}; - -const formatTokenAmount = (amount: number, decimals: number): string => { - const formattedAmount = amount / Math.pow(10, decimals); - if (formattedAmount >= 1_000_000) { - return `${(formattedAmount / 1_000_000).toFixed(2)}M`; - } - if (formattedAmount >= 1_000) { - return `${(formattedAmount / 1_000).toFixed(2)}K`; - } - return formattedAmount.toFixed(decimals > 6 ? 4 : 2); -}; - -const shortenAddress = (address: string): string => { - if (address.length <= 12) return address; - return `${address.slice(0, 6)}...${address.slice(-4)}`; -}; - -const formatTransactionType = (type: string): string => { - switch (type.toLowerCase()) { - case "send": - return "šŸ“¤ Sent"; - case "receive": - return "šŸ“„ Received"; - case "swap": - return "šŸ”„ Swapped"; - case "mint": - return "šŸŒŸ Minted"; - case "burn": - return "šŸ”„ Burned"; - default: - return "šŸ“ Other"; - } -}; - -const formatSingleChainHistory = ( - history: TransactionHistory, - chain: string -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - let response = `Transaction History on ${chainName}:\n\n`; - - if (history.transactions.length === 0) { - return response + "No transactions found."; - } - - history.transactions.forEach((tx, index) => { - const date = new Date(tx.timestamp * 1000).toLocaleString(); - response += `${index + 1}. ${formatTransactionType(tx.type)} - ${date}\n`; - response += `ā€¢ Hash: ${shortenAddress(tx.hash)}\n`; - response += `ā€¢ From: ${shortenAddress(tx.from)}\n`; - response += `ā€¢ To: ${shortenAddress(tx.to)}\n`; - response += `ā€¢ Value: ${formatValue(tx.value)}\n`; - response += `ā€¢ Fee: ${formatValue(tx.fee)}\n`; - response += `ā€¢ Status: ${tx.success ? "āœ… Success" : "āŒ Failed"}\n`; - - if (tx.transfers.length > 0) { - response += "ā€¢ Tokens:\n"; - tx.transfers.forEach((transfer) => { - const amount = formatTokenAmount( - transfer.amount, - transfer.decimals - ); - response += ` - ${amount} ${transfer.symbol} (${formatValue(transfer.value)})\n`; - }); - } - response += "\n"; - }); - - if (history.totalCount > history.transactions.length) { - response += `\nShowing ${history.transactions.length} of ${history.totalCount} total transactions.`; - } - - return response; -}; - -const formatMultichainHistory = ( - history: MultichainTransactionHistory -): string => { - let response = `Multichain Transaction History:\n\n`; - - const chains = Object.keys(history); - if (chains.length === 0) { - return response + "No transactions found on any chain."; - } - - chains.forEach((chain) => { - const chainData = history[chain]; - if (chainData.transactions.length > 0) { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - response += `${chainName} (${chainData.totalCount} total transactions):\n`; - - chainData.transactions - .slice(0, 5) // Show only the 5 most recent transactions per chain - .forEach((tx, index) => { - const date = new Date(tx.timestamp * 1000).toLocaleString(); - response += `${index + 1}. ${formatTransactionType(tx.type)} - ${date}\n`; - response += ` Value: ${formatValue(tx.value)} | Status: ${tx.success ? "āœ…" : "āŒ"}\n`; - }); - - if (chainData.transactions.length > 5) { - response += ` ... and ${chainData.totalCount - 5} more transactions\n`; - } - response += "\n"; - } - }); - - return response; -}; - -export const transactionHistoryProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsTransactionKeyword(messageText)) { - return null; - } - - const walletAddress = extractWalletAddress(messageText); - if (!walletAddress) { - return null; - } - - const isMultichain = isMultichainRequest(messageText); - - if (isMultichain) { - elizaLogger.info( - `MULTICHAIN TRANSACTION HISTORY provider activated for wallet ${walletAddress}` - ); - - const historyData = await getMultichainTransactionHistory( - apiKey, - walletAddress - ); - - if (!historyData) { - return null; - } - - return formatMultichainHistory(historyData); - } else { - const chain = extractChain(messageText); - - elizaLogger.info( - `TRANSACTION HISTORY provider activated for wallet ${walletAddress} on ${chain}` - ); - - const historyData = await getTransactionHistory( - apiKey, - walletAddress, - chain - ); - - if (!historyData) { - return null; - } - - return formatSingleChainHistory(historyData, chain); - } - }, -}; diff --git a/packages/plugin-birdeye/src/providers/wallet/wallet-portfolio-provider.ts b/packages/plugin-birdeye/src/providers/wallet/wallet-portfolio-provider.ts deleted file mode 100644 index 04adbfe98f..0000000000 --- a/packages/plugin-birdeye/src/providers/wallet/wallet-portfolio-provider.ts +++ /dev/null @@ -1,335 +0,0 @@ -import { - IAgentRuntime, - Memory, - Provider, - State, - elizaLogger, -} from "@elizaos/core"; - -// Types -interface TokenBalance { - token: string; - symbol: string; - amount: number; - price: number; - value: number; - decimals: number; - logoURI?: string; -} - -interface PortfolioData { - totalValue: number; - tokens: TokenBalance[]; - lastUpdated: number; -} - -interface MultichainPortfolioData { - chains: Record; - totalValue: number; -} - -// Constants -const PORTFOLIO_KEYWORDS = [ - "portfolio", - "holdings", - "balance", - "assets", - "tokens", - "wallet", - "what do i own", - "what do i have", -] as const; - -const MULTICHAIN_KEYWORDS = [ - "all chains", - "multichain", - "multi-chain", - "cross chain", - "cross-chain", - "every chain", - "all networks", -] as const; - -const CHAIN_KEYWORDS = [ - "solana", - "ethereum", - "arbitrum", - "avalanche", - "bsc", - "optimism", - "polygon", - "base", - "zksync", - "sui", -] as const; - -const BASE_URL = "https://public-api.birdeye.so"; - -// Helper functions -const containsPortfolioKeyword = (text: string): boolean => { - return PORTFOLIO_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const isMultichainRequest = (text: string): boolean => { - return MULTICHAIN_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const extractChain = (text: string): string => { - const chain = CHAIN_KEYWORDS.find((chain) => - text.toLowerCase().includes(chain.toLowerCase()) - ); - return chain || "solana"; -}; - -const extractWalletAddress = (text: string): string | null => { - const words = text.split(/\s+/); - - for (const word of words) { - // Ethereum-like addresses (0x...) - if (/^0x[a-fA-F0-9]{40}$/i.test(word)) { - return word; - } - // Solana addresses (base58, typically 32-44 chars) - if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(word)) { - return word; - } - } - return null; -}; - -const getWalletPortfolio = async ( - apiKey: string, - walletAddress: string, - chain: string = "solana" -): Promise => { - try { - const params = new URLSearchParams({ - wallet: walletAddress, - }); - const url = `${BASE_URL}/wallet/portfolio?${params.toString()}`; - - elizaLogger.info( - `Fetching portfolio for wallet ${walletAddress} on ${chain} from:`, - url - ); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - "x-chain": chain, - }, - }); - - if (!response.ok) { - if (response.status === 404) { - elizaLogger.warn( - `Wallet not found: ${walletAddress} on ${chain}` - ); - return null; - } - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - return data.data; - } catch (error) { - elizaLogger.error("Error fetching wallet portfolio:", error); - return null; - } -}; - -const getMultichainPortfolio = async ( - apiKey: string, - walletAddress: string -): Promise => { - try { - const params = new URLSearchParams({ - wallet: walletAddress, - }); - const url = `${BASE_URL}/wallet/portfolio_multichain?${params.toString()}`; - - elizaLogger.info( - `Fetching multichain portfolio for wallet ${walletAddress} from:`, - url - ); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - }, - }); - - if (!response.ok) { - if (response.status === 404) { - elizaLogger.warn(`Wallet not found: ${walletAddress}`); - return null; - } - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - // Transform the response to match our interface - const { totalValue, ...chains } = data.data; - return { - chains, - totalValue, - }; - } catch (error) { - elizaLogger.error("Error fetching multichain portfolio:", error); - return null; - } -}; - -const formatValue = (value: number): string => { - if (value >= 1_000_000_000) { - return `$${(value / 1_000_000_000).toFixed(2)}B`; - } - if (value >= 1_000_000) { - return `$${(value / 1_000_000).toFixed(2)}M`; - } - if (value >= 1_000) { - return `$${(value / 1_000).toFixed(2)}K`; - } - return `$${value.toFixed(2)}`; -}; - -const formatTokenAmount = (amount: number, decimals: number): string => { - const formattedAmount = amount / Math.pow(10, decimals); - if (formattedAmount >= 1_000_000) { - return `${(formattedAmount / 1_000_000).toFixed(2)}M`; - } - if (formattedAmount >= 1_000) { - return `${(formattedAmount / 1_000).toFixed(2)}K`; - } - return formattedAmount.toFixed(decimals > 6 ? 4 : 2); -}; - -const formatSingleChainPortfolio = ( - data: PortfolioData, - chain: string -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - let response = `Portfolio on ${chainName}:\n\n`; - - response += `šŸ’° Total Value: ${formatValue(data.totalValue)}\n\n`; - - if (data.tokens.length === 0) { - response += "No tokens found in this wallet."; - return response; - } - - response += `Token Holdings:\n`; - data.tokens - .sort((a, b) => b.value - a.value) - .forEach((token) => { - const amount = formatTokenAmount(token.amount, token.decimals); - response += `ā€¢ ${token.symbol}: ${amount} (${formatValue(token.value)})\n`; - }); - - response += `\nLast Updated: ${new Date(data.lastUpdated * 1000).toLocaleString()}`; - return response; -}; - -const formatMultichainPortfolio = (data: MultichainPortfolioData): string => { - let response = `Multichain Portfolio Overview:\n\n`; - response += `šŸ’° Total Portfolio Value: ${formatValue(data.totalValue)}\n\n`; - - const chains = Object.keys(data.chains); - if (chains.length === 0) { - response += "No assets found across any chains."; - return response; - } - - chains - .sort((a, b) => data.chains[b].totalValue - data.chains[a].totalValue) - .forEach((chain) => { - const chainData = data.chains[chain]; - if (chainData.totalValue > 0) { - const chainName = - chain.charAt(0).toUpperCase() + chain.slice(1); - response += `${chainName} (${formatValue(chainData.totalValue)}):\n`; - chainData.tokens - .sort((a, b) => b.value - a.value) - .slice(0, 5) // Show top 5 tokens per chain - .forEach((token) => { - const amount = formatTokenAmount( - token.amount, - token.decimals - ); - response += `ā€¢ ${token.symbol}: ${amount} (${formatValue(token.value)})\n`; - }); - if (chainData.tokens.length > 5) { - response += ` ... and ${chainData.tokens.length - 5} more tokens\n`; - } - response += "\n"; - } - }); - - return response; -}; - -export const walletPortfolioProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - return null; - } - - const messageText = message.content.text; - - if (!containsPortfolioKeyword(messageText)) { - return null; - } - - const walletAddress = extractWalletAddress(messageText); - if (!walletAddress) { - return null; - } - - const isMultichain = isMultichainRequest(messageText); - - if (isMultichain) { - elizaLogger.info( - `MULTICHAIN PORTFOLIO provider activated for wallet ${walletAddress}` - ); - - const portfolioData = await getMultichainPortfolio( - apiKey, - walletAddress - ); - - if (!portfolioData) { - return null; - } - - return formatMultichainPortfolio(portfolioData); - } else { - const chain = extractChain(messageText); - - elizaLogger.info( - `PORTFOLIO provider activated for wallet ${walletAddress} on ${chain}` - ); - - const portfolioData = await getWalletPortfolio( - apiKey, - walletAddress, - chain - ); - - if (!portfolioData) { - return null; - } - - return formatSingleChainPortfolio(portfolioData, chain); - } - }, -}; diff --git a/packages/plugin-birdeye/src/services.ts b/packages/plugin-birdeye/src/services.ts new file mode 100644 index 0000000000..15b6decee3 --- /dev/null +++ b/packages/plugin-birdeye/src/services.ts @@ -0,0 +1,170 @@ +import { elizaLogger } from "@elizaos/core"; +import { + SearchToken, + SearchTokenResponse, + SearchTokensOptions, +} from "./types/search-token"; +import { BirdeyeChain } from "./types/shared"; +import { TokenMetadataResponse } from "./types/token-metadata"; +import { + WalletDataItem, + WalletDataOptions, + WalletDataResponse, +} from "./types/wallet"; +import { BASE_URL, makeApiRequest } from "./utils"; + +export const searchTokens = async ( + apiKey: string, + options: SearchTokensOptions +): Promise => { + try { + const { keyword, chain = "all", limit = 1, offset = 0, type } = options; + + const params = new URLSearchParams({ + keyword, + limit: limit.toString(), + offset: offset.toString(), + chain: chain, + }); + + const url = `${BASE_URL}/defi/v3/search?${params.toString()}`; + + elizaLogger.info("Searching tokens from:", url); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = (await response.json()) as SearchTokenResponse; + + elizaLogger.info("Birdeye response:", data); + + // Extract tokens from the response + // if the search type is address, we only want to return the token that matches the address + const tokens = + type === "address" + ? data.data.items + .filter( + (item) => + item.type === "token" && + item.result[0].address === keyword.toLowerCase() + ) + .flatMap((item) => item.result) + : data.data.items + .filter((item) => item.type === "token") + .flatMap((item) => item.result); + + elizaLogger.info("Found tokens:", tokens); + + return tokens; + } catch (error) { + elizaLogger.error("Error searching tokens:", error); + throw error; + } +}; + +export const searchWallets = async ( + apiKey: string, + options: WalletDataOptions +): Promise => { + try { + const { wallet, chain = "solana" } = options; + + const params = new URLSearchParams({ + wallet, + chain: chain, + }); + + const url = `${BASE_URL}/v1/wallet/token_list?${params.toString()}`; + + elizaLogger.info("Searching wallet data from:", url); + + const response = await fetch(url, { + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = (await response.json()) as WalletDataResponse; + + elizaLogger.info("Birdeye response:", data); + + // Extract tokens from the response + // if the search type is address, we only want to return the token that matches the address + const walletData = data.data.items; + + elizaLogger.info("Found wallet data:", walletData); + + return walletData; + } catch (error) { + elizaLogger.error("Error searching tokens:", error); + throw error; + } +}; + +export const getTokenMetadata = async ( + apiKey: string, + address: string, + chain: BirdeyeChain +): Promise => { + try { + // Validate address format based on chain + const isValidAddress = (() => { + switch (chain) { + case "solana": + return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address); + case "sui": + return /^0x[a-fA-F0-9]{64}$/i.test(address); + case "ethereum": + case "arbitrum": + case "avalanche": + case "bsc": + case "optimism": + case "polygon": + case "base": + case "zksync": + return /^0x[a-fA-F0-9]{40}$/i.test(address); + default: + return false; + } + })(); + + if (!isValidAddress) { + elizaLogger.error( + `Invalid address format for ${chain}: ${address}` + ); + return null; + } + + const params = new URLSearchParams({ + address: address, + }); + const url = `${BASE_URL}/defi/v3/token/meta-data/single?${params.toString()}`; + + elizaLogger.info( + `Fetching token metadata for ${address} on ${chain} from:`, + url + ); + + return await makeApiRequest(url, { + apiKey, + chain, + }); + } catch (error) { + if (error instanceof Error) { + elizaLogger.error("Error fetching token metadata:", error.message); + } + return null; + } +}; diff --git a/packages/plugin-birdeye/src/types/search-token.ts b/packages/plugin-birdeye/src/types/search-token.ts new file mode 100644 index 0000000000..10c150c1ec --- /dev/null +++ b/packages/plugin-birdeye/src/types/search-token.ts @@ -0,0 +1,43 @@ +export interface SearchToken { + address: string; + name: string; + symbol: string; + price: number; + fdv: number; + market_cap: number; + liquidity: number; + volume_24h_usd: number; + volume_24h_change_percent: number; + price_change_24h_percent: number; + network: string; + buy_24h: number; + buy_24h_change_percent: number; + sell_24h: number; + sell_24h_change_percent: number; + trade_24h: number; + trade_24h_change_percent: number; + unique_wallet_24h: number; + unique_view_24h_change_percent: number; + last_trade_human_time: string; + last_trade_unix_time: number; + logo_uri: string; + verified: boolean; +} + +export interface SearchTokenResponse { + data: { + items: Array<{ + type: string; + result: SearchToken[]; + }>; + }; + success: boolean; +} + +export interface SearchTokensOptions { + keyword: string; + chain?: string; + limit?: number; + offset?: number; + type?: "address" | "symbol"; +} diff --git a/packages/plugin-birdeye/src/types/shared.ts b/packages/plugin-birdeye/src/types/shared.ts new file mode 100644 index 0000000000..1106716e15 --- /dev/null +++ b/packages/plugin-birdeye/src/types/shared.ts @@ -0,0 +1,23 @@ +import { CHAIN_KEYWORDS } from "../utils"; + +// Types +export type BirdeyeChain = (typeof CHAIN_KEYWORDS)[number]; + +export interface BaseAddress { + type?: "wallet" | "token" | "contract"; + symbol?: string; + address: string; + chain: BirdeyeChain; +} + +export interface WalletAddress extends BaseAddress { + type: "wallet"; +} + +export interface TokenAddress extends BaseAddress { + type: "token"; +} + +export interface ContractAddress extends BaseAddress { + type: "contract"; +} diff --git a/packages/plugin-birdeye/src/types/token-metadata.ts b/packages/plugin-birdeye/src/types/token-metadata.ts new file mode 100644 index 0000000000..935837896d --- /dev/null +++ b/packages/plugin-birdeye/src/types/token-metadata.ts @@ -0,0 +1,18 @@ +// Define explicit interface instead of using typeof +export interface TokenMetadataResponse { + data: { + address: string; + symbol: string; + name: string; + decimals: number; + extensions: { + coingecko_id?: string; + website?: string; + twitter?: string; + discord?: string; + medium?: string; + }; + logo_uri?: string; + }; + success: boolean; +} diff --git a/packages/plugin-birdeye/src/types/wallet.ts b/packages/plugin-birdeye/src/types/wallet.ts new file mode 100644 index 0000000000..2d97d5a763 --- /dev/null +++ b/packages/plugin-birdeye/src/types/wallet.ts @@ -0,0 +1,24 @@ +export interface WalletDataResponse { + data: { + items: WalletDataItem[]; + }; + success: boolean; +} + +export interface WalletDataItem { + address: string; + name: string; + symbol: string; + decimals: number; + balance: string; + uiAmount: number; + chainId: string; + logoURI: string; + priceUsd: number; + valueUsd: number; +} + +export interface WalletDataOptions { + wallet: string; + chain?: string; +} diff --git a/packages/plugin-birdeye/src/utils.ts b/packages/plugin-birdeye/src/utils.ts new file mode 100644 index 0000000000..d93655cc93 --- /dev/null +++ b/packages/plugin-birdeye/src/utils.ts @@ -0,0 +1,546 @@ +import { elizaLogger } from "@elizaos/core"; +import { SearchToken } from "./types/search-token"; +import { BaseAddress, BirdeyeChain } from "./types/shared"; +import { TokenMetadataResponse } from "./types/token-metadata"; + +// Constants +export const BASE_URL = "https://public-api.birdeye.so"; + +export const CHAIN_KEYWORDS = [ + "solana", + "ethereum", + "arbitrum", + "avalanche", + "bsc", + "optimism", + "polygon", + "base", + "zksync", + "sui", + "solana", + "evm", // EVM-compatible chains but we don't know the chain +] as const; + +// Chain abbreviations and alternative names mapping +export const CHAIN_ALIASES: Record = { + // Solana + sol: "solana", + + // Ethereum + eth: "ethereum", + ether: "ethereum", + + // Arbitrum + arb: "arbitrum", + arbitrumone: "arbitrum", + + // Avalanche + avax: "avalanche", + + // BSC + bnb: "bsc", + binance: "bsc", + "binance smart chain": "bsc", + + // Optimism + op: "optimism", + opti: "optimism", + + // Polygon + matic: "polygon", + poly: "polygon", + + // Base + // no common abbreviations + + // zkSync + zks: "zksync", + zk: "zksync", + + // Sui + // no common abbreviations +} as const; + +export class BirdeyeApiError extends Error { + constructor( + public status: number, + message: string + ) { + super(message); + this.name = "BirdeyeApiError"; + } +} + +export interface ApiResponse { + success: boolean; + data: T; + error?: string; +} + +// Time-related types and constants +export const TIME_UNITS = { + second: 1, + minute: 60, + hour: 3600, + day: 86400, + week: 604800, + month: 2592000, +} as const; + +export const TIMEFRAME_KEYWORDS = { + "1m": 60, + "3m": 180, + "5m": 300, + "15m": 900, + "30m": 1800, + "1h": 3600, + "2h": 7200, + "4h": 14400, + "6h": 21600, + "12h": 43200, + "1d": 86400, + "1w": 604800, +} as const; + +export type TimeUnit = keyof typeof TIME_UNITS; +export type Timeframe = keyof typeof TIMEFRAME_KEYWORDS; + +// Helper functions +export const extractChain = (text: string): BirdeyeChain => { + // Check for SUI address (0x followed by 64 hex chars) + if (text.match(/0x[a-fA-F0-9]{64}/)) { + return "sui"; + } + // Check for EVM address (0x followed by 40 hex chars) + if (text.match(/0x[a-fA-F0-9]{40}/)) { + return "ethereum"; + } + // Default to solana + return "solana"; +}; + +export const extractContractAddresses = (text: string): BaseAddress[] => { + const addresses: BaseAddress[] = []; + + // EVM-compatible chains (Ethereum, Arbitrum, Avalanche, BSC, Optimism, Polygon, Base, zkSync) + const evmAddresses = text.match(/0x[a-fA-F0-9]{40}/g); + if (evmAddresses) { + addresses.push( + ...evmAddresses.map((address) => ({ + address, + chain: extractChain(address), + })) + ); + } + + // Solana addresses (base58 strings) + const solAddresses = text.match(/[1-9A-HJ-NP-Za-km-z]{32,44}/g); + if (solAddresses) { + addresses.push( + ...solAddresses.map((address) => ({ + address, + chain: "solana" as BirdeyeChain, + })) + ); + } + + // Sui addresses (0x followed by 64 hex chars) + const suiAddresses = text.match(/0x[a-fA-F0-9]{64}/g); + if (suiAddresses) { + addresses.push( + ...suiAddresses.map((address) => ({ + address, + chain: "sui" as BirdeyeChain, + })) + ); + } + + return addresses; +}; + +// Time extraction and analysis +export const extractTimeframe = (text: string): Timeframe => { + // First, check for explicit timeframe mentions + const timeframe = Object.keys(TIMEFRAME_KEYWORDS).find((tf) => + text.toLowerCase().includes(tf.toLowerCase()) + ); + if (timeframe) return timeframe as Timeframe; + + // Check for semantic timeframe hints + const semanticMap = { + "short term": "15m", + "medium term": "1h", + "long term": "1d", + intraday: "1h", + daily: "1d", + weekly: "1w", + detailed: "5m", + quick: "15m", + overview: "1d", + } as const; + + for (const [hint, tf] of Object.entries(semanticMap)) { + if (text.toLowerCase().includes(hint)) { + return tf as Timeframe; + } + } + + // Analyze for time-related words + if (text.match(/minute|min|minutes/i)) return "15m"; + if (text.match(/hour|hourly|hours/i)) return "1h"; + if (text.match(/day|daily|24h/i)) return "1d"; + if (text.match(/week|weekly/i)) return "1w"; + + // Default based on context + if (text.match(/trade|trades|trading|recent/i)) return "15m"; + if (text.match(/trend|analysis|analyze/i)) return "1h"; + if (text.match(/history|historical|long|performance/i)) return "1d"; + + return "1h"; // Default timeframe +}; + +export const extractTimeRange = ( + text: string +): { start: number; end: number } => { + const now = Math.floor(Date.now() / 1000); + + // Check for specific date ranges + const dateRangeMatch = text.match( + /from\s+(\d{4}-\d{2}-\d{2})\s+to\s+(\d{4}-\d{2}-\d{2})/i + ); + if (dateRangeMatch) { + const start = new Date(dateRangeMatch[1]).getTime() / 1000; + const end = new Date(dateRangeMatch[2]).getTime() / 1000; + return { start, end }; + } + + // Check for relative time expressions + const timeRegex = /(\d+)\s*(second|minute|hour|day|week|month)s?\s*ago/i; + const match = text.match(timeRegex); + if (match) { + const amount = parseInt(match[1]); + const unit = match[2].toLowerCase() as TimeUnit; + const start = now - amount * TIME_UNITS[unit]; + return { start, end: now }; + } + + // Check for semantic time ranges + const semanticRanges: Record = { + today: TIME_UNITS.day, + "this week": TIME_UNITS.week, + "this month": TIME_UNITS.month, + recent: TIME_UNITS.hour * 4, + latest: TIME_UNITS.hour, + "last hour": TIME_UNITS.hour, + "last day": TIME_UNITS.day, + "last week": TIME_UNITS.week, + "last month": TIME_UNITS.month, + }; + + for (const [range, duration] of Object.entries(semanticRanges)) { + if (text.toLowerCase().includes(range)) { + return { start: now - duration, end: now }; + } + } + + // Analyze context for appropriate default range + if (text.match(/trend|analysis|performance/i)) { + return { start: now - TIME_UNITS.week, end: now }; // 1 week for analysis + } + if (text.match(/trade|trades|trading|recent/i)) { + return { start: now - TIME_UNITS.day, end: now }; // 1 day for trading + } + if (text.match(/history|historical|long term/i)) { + return { start: now - TIME_UNITS.month, end: now }; // 1 month for history + } + + // Default to last 24 hours + return { start: now - TIME_UNITS.day, end: now }; +}; + +export const extractLimit = (text: string): number => { + // Check for explicit limit mentions + const limitMatch = text.match( + /\b(show|display|get|fetch|limit)\s+(\d+)\b/i + ); + if (limitMatch) { + const limit = parseInt(limitMatch[2]); + return Math.min(Math.max(limit, 1), 100); // Clamp between 1 and 100 + } + + // Check for semantic limit hints + if (text.match(/\b(all|everything|full|complete)\b/i)) return 100; + if (text.match(/\b(brief|quick|summary|overview)\b/i)) return 5; + if (text.match(/\b(detailed|comprehensive)\b/i)) return 50; + + // Default based on context + if (text.match(/\b(trade|trades|trading)\b/i)) return 10; + if (text.match(/\b(analysis|analyze|trend)\b/i)) return 24; + if (text.match(/\b(history|historical)\b/i)) return 50; + + return 10; // Default limit +}; + +// Formatting helpers +export const formatValue = (value: number): string => { + if (value >= 1_000_000_000) { + return `$${(value / 1_000_000_000).toFixed(2)}B`; + } + if (value >= 1_000_000) { + return `$${(value / 1_000_000).toFixed(2)}M`; + } + if (value >= 1_000) { + return `$${(value / 1_000).toFixed(2)}K`; + } + return `$${value.toFixed(2)}`; +}; + +export const formatPercentChange = (change?: number): string => { + if (change === undefined) return "N/A"; + const symbol = change >= 0 ? "šŸ“ˆ" : "šŸ“‰"; + return `${symbol} ${Math.abs(change).toFixed(2)}%`; +}; + +export const shortenAddress = (address: string): string => { + if (!address || address.length <= 12) return address || "Unknown"; + return `${address.slice(0, 6)}...${address.slice(-4)}`; +}; + +export const formatTimestamp = (timestamp: number): string => { + return new Date(timestamp * 1000).toLocaleString(); +}; + +export const formatPrice = (price: number): string => { + return price < 0.01 ? price.toExponential(2) : price.toFixed(2); +}; + +// API helpers +export async function makeApiRequest( + url: string, + options: { + apiKey: string; + chain?: BirdeyeChain; + method?: "GET" | "POST"; + body?: any; + } +): Promise { + const { apiKey, chain = "solana", method = "GET", body } = options; + + try { + const response = await fetch(url, { + method, + headers: { + "X-API-KEY": apiKey, + "x-chain": chain, + ...(body && { "Content-Type": "application/json" }), + }, + ...(body && { body: JSON.stringify(body) }), + }); + + if (!response.ok) { + if (response.status === 404) { + throw new BirdeyeApiError(404, "Resource not found"); + } + if (response.status === 429) { + throw new BirdeyeApiError(429, "Rate limit exceeded"); + } + throw new BirdeyeApiError( + response.status, + `HTTP error! status: ${response.status}` + ); + } + + const responseJson: T = await response.json(); + + return responseJson; + } catch (error) { + if (error instanceof BirdeyeApiError) { + elizaLogger.error(`API Error (${error.status}):`, error.message); + } else { + elizaLogger.error("Error making API request:", error); + } + throw error; + } +} + +// Formatting helpers +export const formatTokenInfo = ( + token: SearchToken, + metadata?: TokenMetadataResponse +): string => { + const priceFormatted = + token.price != null + ? token.price < 0.01 + ? token.price.toExponential(2) + : token.price.toFixed(2) + : "N/A"; + + const volume = + token.volume_24h_usd != null + ? `$${(token.volume_24h_usd / 1_000_000).toFixed(2)}M` + : "N/A"; + + const liquidity = + token.liquidity != null + ? `$${(token.liquidity / 1_000_000).toFixed(2)}M` + : "N/A"; + + const fdv = + token.fdv != null ? `$${(token.fdv / 1_000_000).toFixed(2)}M` : "N/A"; + + const priceChange = + token.price_change_24h_percent != null + ? `${token.price_change_24h_percent > 0 ? "+" : ""}${token.price_change_24h_percent.toFixed(2)}%` + : "N/A"; + + const trades = token.trade_24h != null ? token.trade_24h.toString() : "N/A"; + + const age = token.last_trade_unix_time + ? `${Math.floor((Date.now() - new Date(token.last_trade_unix_time).getTime()) / (1000 * 60 * 60 * 24))}d` + : "N/A"; + + let output = + `šŸŖ™ ${token.name} @ ${token.symbol}\n` + + `šŸ’° USD: $${priceFormatted} (${priceChange})\n` + + `šŸ’Ž FDV: ${fdv}\n` + + `šŸ’¦ Liq: ${liquidity}\n` + + `šŸ“Š Vol: ${volume} šŸ•°ļø Age: ${age}\n` + + `šŸ”„ Trades: ${trades}\n` + + `šŸ”— Address: ${token.address}`; + + // Add metadata if available + if (metadata?.success) { + const { extensions } = metadata.data; + const links: string[] = []; + + if (extensions.website) + links.push(`šŸŒ [Website](${extensions.website})`); + if (extensions.twitter) + links.push(`šŸ¦ [Twitter](${extensions.twitter})`); + if (extensions.discord) + links.push(`šŸ’¬ [Discord](${extensions.discord})`); + if (extensions.medium) links.push(`šŸ“ [Medium](${extensions.medium})`); + if (extensions.coingecko_id) + links.push( + `šŸ¦Ž [CoinGecko](https://www.coingecko.com/en/coins/${extensions.coingecko_id})` + ); + + if (links.length > 0) { + output += "\n\nšŸ“± Social Links:\n" + links.join("\n"); + } + } + + return output; +}; + +// Extract symbols from text +export const extractSymbols = (text: string): string[] => { + const symbols = new Set(); + + // Match symbols after "a" or "an" (e.g., "a BTC" or "an ETH") + const afterArticles = text.matchAll(/\b(?:a|an)\s+([A-Z]{2,10})\b/gi); + for (const match of afterArticles) { + symbols.add(match[1].toUpperCase()); + } + + // Match standalone acronyms (2-10 chars, all caps) + const acronyms = text.matchAll(/\b[A-Z]{2,10}\b/g); + for (const match of acronyms) { + symbols.add(match[0]); + } + + // Match token symbols in quotes (e.g., "BTC" or 'ETH') + const quotedSymbols = text.matchAll(/["']([A-Z]{2,10})["']/gi); + for (const match of quotedSymbols) { + symbols.add(match[1].toUpperCase()); + } + + return Array.from(symbols); +}; + +export const formatMetadataResponse = ( + data: TokenMetadataResponse, + chain: BirdeyeChain +): string => { + const tokenData = data.data; + const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); + const chainExplorer = (() => { + switch (chain) { + case "solana": + return `https://solscan.io/token/${tokenData.address}`; + case "ethereum": + return `https://etherscan.io/token/${tokenData.address}`; + case "arbitrum": + return `https://arbiscan.io/token/${tokenData.address}`; + case "avalanche": + return `https://snowtrace.io/token/${tokenData.address}`; + case "bsc": + return `https://bscscan.com/token/${tokenData.address}`; + case "optimism": + return `https://optimistic.etherscan.io/token/${tokenData.address}`; + case "polygon": + return `https://polygonscan.com/token/${tokenData.address}`; + case "base": + return `https://basescan.org/token/${tokenData.address}`; + case "zksync": + return `https://explorer.zksync.io/address/${tokenData.address}`; + case "sui": + return `https://suiscan.xyz/mainnet/object/${tokenData.address}`; + default: + return null; + } + })(); + + let response = `Token Metadata for ${tokenData.name} (${tokenData.symbol}) on ${chainName}\n\n`; + + // Basic Information + response += "šŸ“ Basic Information\n"; + response += `ā€¢ Name: ${tokenData.name}\n`; + response += `ā€¢ Symbol: ${tokenData.symbol}\n`; + response += `ā€¢ Address: ${tokenData.address}\n`; + response += `ā€¢ Decimals: ${tokenData.decimals}\n`; + if (chainExplorer) { + response += `ā€¢ Explorer: [View on ${chainName} Explorer](${chainExplorer})\n`; + } + + // Social Links + response += "\nšŸ”— Social Links & Extensions\n"; + response += formatSocialLinks(tokenData) + "\n"; + + // Logo + if (tokenData.logo_uri) { + response += "\nšŸ–¼ļø Logo\n"; + response += tokenData.logo_uri; + } + + return response; +}; + +const formatSocialLinks = (data: TokenMetadataResponse["data"]): string => { + const links: string[] = []; + const { extensions } = data; + + if (!extensions) { + return "No social links available"; + } + + if (extensions.website) { + links.push(`šŸŒ [Website](${extensions.website})`); + } + if (extensions.twitter) { + links.push(`šŸ¦ [Twitter](${extensions.twitter})`); + } + if (extensions.discord) { + links.push(`šŸ’¬ [Discord](${extensions.discord})`); + } + if (extensions.medium) { + links.push(`šŸ“ [Medium](${extensions.medium})`); + } + if (extensions.coingecko_id) { + links.push( + `šŸ¦Ž [CoinGecko](https://www.coingecko.com/en/coins/${extensions.coingecko_id})` + ); + } + + return links.length > 0 ? links.join("\n") : "No social links available"; +}; diff --git a/packages/plugin-solana/src/evaluators/trust.ts b/packages/plugin-solana/src/evaluators/trust.ts index 2c4f441cf5..1837cff970 100644 --- a/packages/plugin-solana/src/evaluators/trust.ts +++ b/packages/plugin-solana/src/evaluators/trust.ts @@ -1,22 +1,23 @@ import { + ActionExample, + booleanFooter, composeContext, + Content, + elizaLogger, + Evaluator, generateObjectArray, generateTrueOrFalse, - MemoryManager, - booleanFooter, - ActionExample, - Content, IAgentRuntime, Memory, + MemoryManager, ModelClass, - Evaluator, } from "@elizaos/core"; -import { TrustScoreManager } from "../providers/trustScoreProvider.ts"; -import { TokenProvider } from "../providers/token.ts"; -import { WalletProvider } from "../providers/wallet.ts"; import { TrustScoreDatabase } from "@elizaos/plugin-trustdb"; import { Connection } from "@solana/web3.js"; import { getWalletKey } from "../keypairUtils.ts"; +import { TokenProvider } from "../providers/token.ts"; +import { TrustScoreManager } from "../providers/trustScoreProvider.ts"; +import { WalletProvider } from "../providers/wallet.ts"; const shouldProcessTemplate = `# Task: Decide if the recent messages should be processed for token recommendations. @@ -80,6 +81,13 @@ Response should be a JSON object array inside a JSON markdown block. Correct res async function handler(runtime: IAgentRuntime, message: Memory) { console.log("Evaluating for trust"); + + // if the database type is postgres, we don't want to run this because it relies on sql queries that are currently specific to sqlite. This check can be removed once the trust score provider is updated to work with postgres. + if (runtime.getSetting("POSTGRES_URL")) { + elizaLogger.warn("skipping trust evaluator because db is postgres"); + return []; + } + const state = await runtime.composeState(message); const { agentId, roomId } = state; @@ -186,7 +194,6 @@ async function handler(runtime: IAgentRuntime, message: Memory) { } // create the trust score manager - const trustScoreDb = new TrustScoreDatabase(runtime.databaseAdapter.db); const trustScoreManager = new TrustScoreManager( runtime, diff --git a/packages/plugin-solana/src/index.ts b/packages/plugin-solana/src/index.ts index b207ed260b..cd632ee8e0 100644 --- a/packages/plugin-solana/src/index.ts +++ b/packages/plugin-solana/src/index.ts @@ -1,20 +1,18 @@ +export * from "./evaluators/trust.ts"; export * from "./providers/token.ts"; -export * from "./providers/wallet.ts"; export * from "./providers/trustScoreProvider.ts"; -export * from "./evaluators/trust.ts"; +export * from "./providers/wallet.ts"; import { Plugin } from "@elizaos/core"; -import { executeSwap } from "./actions/swap.ts"; -import take_order from "./actions/takeOrder"; -import pumpfun from "./actions/pumpfun.ts"; import fomo from "./actions/fomo.ts"; +import pumpfun from "./actions/pumpfun.ts"; +import { executeSwap } from "./actions/swap.ts"; import { executeSwapForDAO } from "./actions/swapDao"; +import take_order from "./actions/takeOrder"; import transferToken from "./actions/transfer.ts"; -import { walletProvider } from "./providers/wallet.ts"; -import { trustScoreProvider } from "./providers/trustScoreProvider.ts"; import { trustEvaluator } from "./evaluators/trust.ts"; import { TokenProvider } from "./providers/token.ts"; -import { WalletProvider } from "./providers/wallet.ts"; +import { walletProvider, WalletProvider } from "./providers/wallet.ts"; export { TokenProvider, WalletProvider }; @@ -30,7 +28,8 @@ export const solanaPlugin: Plugin = { take_order, ], evaluators: [trustEvaluator], - providers: [walletProvider, trustScoreProvider], + // providers: [walletProvider, trustScoreProvider], + providers: [walletProvider], }; export default solanaPlugin; diff --git a/packages/plugin-solana/src/providers/trustScoreProvider.ts b/packages/plugin-solana/src/providers/trustScoreProvider.ts index 931cd9b44d..4825511706 100644 --- a/packages/plugin-solana/src/providers/trustScoreProvider.ts +++ b/packages/plugin-solana/src/providers/trustScoreProvider.ts @@ -1,26 +1,25 @@ import { - ProcessedTokenData, - TokenSecurityData, - // TokenTradeData, - // DexScreenerData, - // DexScreenerPair, - // HolderData, -} from "../types/token.ts"; -import { Connection, PublicKey } from "@solana/web3.js"; -import { getAssociatedTokenAddress } from "@solana/spl-token"; -import { TokenProvider } from "./token.ts"; -import { WalletProvider } from "./wallet.ts"; -import { SimulationSellingService } from "./simulationSellingService.ts"; + elizaLogger, + IAgentRuntime, + Memory, + Provider, + settings, + State, +} from "@elizaos/core"; import { - TrustScoreDatabase, RecommenderMetrics, TokenPerformance, - TradePerformance, TokenRecommendation, + TradePerformance, + TrustScoreDatabase, } from "@elizaos/plugin-trustdb"; -import { settings } from "@elizaos/core"; -import { IAgentRuntime, Memory, Provider, State } from "@elizaos/core"; +import { getAssociatedTokenAddress } from "@solana/spl-token"; +import { Connection, PublicKey } from "@solana/web3.js"; import { v4 as uuidv4 } from "uuid"; +import { ProcessedTokenData, TokenSecurityData } from "../types/token.ts"; +import { SimulationSellingService } from "./simulationSellingService.ts"; +import { TokenProvider } from "./token.ts"; +import { WalletProvider } from "./wallet.ts"; const Wallet = settings.MAIN_WALLET_ADDRESS; interface TradeData { @@ -702,6 +701,14 @@ export const trustScoreProvider: Provider = { _state?: State ): Promise { try { + // if the database type is postgres, we don't want to run this because it relies on sql queries that are currently specific to sqlite. This check can be removed once the trust score provider is updated to work with postgres. + if (runtime.getSetting("POSTGRES_URL")) { + elizaLogger.warn( + "skipping trust evaluator because db is postgres" + ); + return ""; + } + const trustScoreDb = new TrustScoreDatabase( runtime.databaseAdapter.db ); diff --git a/src/providers/address-search.provider.ts b/src/providers/address-search.provider.ts new file mode 100644 index 0000000000..7e6f2f4342 --- /dev/null +++ b/src/providers/address-search.provider.ts @@ -0,0 +1,9 @@ +/** + * Searches message text for contract addresses, symbols, or wallet addresses and enriches them with: + * - Portfolio data from wallet addresses + * - Token metadata from contract addresses/symbols + * Queries endpoints in parallel and aggregates results for agent context. + */ +export class AddressSearchProvider { + // ... rest of code +} From 0c51d1180211dafd4b0159aa60c19069069ab9e4 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Sat, 28 Dec 2024 09:26:29 -0800 Subject: [PATCH 04/33] chore: cleanup filter logic --- .../src/providers/address-search-provider.ts | 13 +++-- packages/plugin-birdeye/src/services.ts | 10 ++-- packages/plugin-birdeye/src/utils.ts | 54 +++++++++++++------ 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/packages/plugin-birdeye/src/providers/address-search-provider.ts b/packages/plugin-birdeye/src/providers/address-search-provider.ts index 40f178a762..2f60ee08d6 100644 --- a/packages/plugin-birdeye/src/providers/address-search-provider.ts +++ b/packages/plugin-birdeye/src/providers/address-search-provider.ts @@ -51,9 +51,7 @@ export const addressSearchProvider: Provider = { }).then((results) => ({ searchTerm: address.address, address: address.address, - // find the result that matches the address - result: - results.find((r) => r.address === address.address) || null, + result: results[0] || null, })) ); @@ -66,8 +64,7 @@ export const addressSearchProvider: Provider = { searchTerm: symbol, symbol: results[0]?.symbol || null, address: results[0]?.address || null, - // find the result that matches the symbol - result: results.find((r) => r.symbol === symbol) || null, + result: results[0] || null, })) ); @@ -77,6 +74,12 @@ export const addressSearchProvider: Provider = { ]); const validResults = results.filter((r) => r.result !== null); + elizaLogger.info( + `Found ${validResults.length} valid results for ${addresses.length} addresses and ${symbols.length} symbols` + ); + + console.log(JSON.stringify(validResults, null, 2)); + // bail if no valid results if (validResults.length === 0) return null; diff --git a/packages/plugin-birdeye/src/services.ts b/packages/plugin-birdeye/src/services.ts index 15b6decee3..ef508b4d00 100644 --- a/packages/plugin-birdeye/src/services.ts +++ b/packages/plugin-birdeye/src/services.ts @@ -43,8 +43,6 @@ export const searchTokens = async ( const data = (await response.json()) as SearchTokenResponse; - elizaLogger.info("Birdeye response:", data); - // Extract tokens from the response // if the search type is address, we only want to return the token that matches the address const tokens = @@ -53,11 +51,17 @@ export const searchTokens = async ( .filter( (item) => item.type === "token" && + // only return the token that matches the address item.result[0].address === keyword.toLowerCase() ) .flatMap((item) => item.result) : data.data.items - .filter((item) => item.type === "token") + .filter( + (item) => + item.type === "token" && + // only return the token that matches the symbol + item.result[0].symbol === keyword + ) .flatMap((item) => item.result); elizaLogger.info("Found tokens:", tokens); diff --git a/packages/plugin-birdeye/src/utils.ts b/packages/plugin-birdeye/src/utils.ts index d93655cc93..ec8d559171 100644 --- a/packages/plugin-birdeye/src/utils.ts +++ b/packages/plugin-birdeye/src/utils.ts @@ -437,23 +437,43 @@ export const formatTokenInfo = ( export const extractSymbols = (text: string): string[] => { const symbols = new Set(); - // Match symbols after "a" or "an" (e.g., "a BTC" or "an ETH") - const afterArticles = text.matchAll(/\b(?:a|an)\s+([A-Z]{2,10})\b/gi); - for (const match of afterArticles) { - symbols.add(match[1].toUpperCase()); - } - - // Match standalone acronyms (2-10 chars, all caps) - const acronyms = text.matchAll(/\b[A-Z]{2,10}\b/g); - for (const match of acronyms) { - symbols.add(match[0]); - } - - // Match token symbols in quotes (e.g., "BTC" or 'ETH') - const quotedSymbols = text.matchAll(/["']([A-Z]{2,10})["']/gi); - for (const match of quotedSymbols) { - symbols.add(match[1].toUpperCase()); - } + // Common words to exclude (avoid false positives) + const excludeWords = new Set([ + "USD", + "APY", + "API", + "NFT", + "DEX", + "CEX", + "APR", + "TVL", + ]); + + // Match patterns: + const patterns = [ + // $SYMBOL format + /\$([A-Z0-9]{2,10})\b/gi, + // After articles (a/an) + /\b(?:a|an)\s+([A-Z0-9]{2,10})\b/gi, + // Standalone caps + /\b[A-Z0-9]{2,10}\b/g, + // Quoted symbols + /["']([A-Z0-9]{2,10})["']/gi, + // Common price patterns + /\b([A-Z0-9]{2,10})\/USD\b/gi, + /\b([A-Z0-9]{2,10})-USD\b/gi, + ]; + + // Extract all matches + patterns.forEach((pattern) => { + const matches = text.matchAll(pattern); + for (const match of matches) { + const symbol = (match[1] || match[0]).toUpperCase(); + if (!excludeWords.has(symbol)) { + symbols.add(symbol); + } + } + }); return Array.from(symbols); }; From 10122e24a756fd84e062d7ab6e1b434be4042bae Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Sat, 28 Dec 2024 09:38:41 -0800 Subject: [PATCH 05/33] chore: incorporate from PR 1366 --- packages/plugin-birdeye/src/actions/report.ts | 179 ++++++++++ packages/plugin-birdeye/src/index.ts | 5 +- .../plugin-birdeye/src/providers/birdeye.ts | 324 ++++++++++++++++++ .../plugin-birdeye/src/tests/birdeye.test.ts | 292 ++++++++++++++++ 4 files changed, 799 insertions(+), 1 deletion(-) create mode 100644 packages/plugin-birdeye/src/actions/report.ts create mode 100644 packages/plugin-birdeye/src/providers/birdeye.ts create mode 100644 packages/plugin-birdeye/src/tests/birdeye.test.ts diff --git a/packages/plugin-birdeye/src/actions/report.ts b/packages/plugin-birdeye/src/actions/report.ts new file mode 100644 index 0000000000..abb34dddb5 --- /dev/null +++ b/packages/plugin-birdeye/src/actions/report.ts @@ -0,0 +1,179 @@ +import { + Action, + ActionExample, + composeContext, + elizaLogger, + generateText, + ModelClass, + type IAgentRuntime, + type Memory, + type State, +} from "@elizaos/core"; +import { BirdeyeProvider } from "../providers/birdeye"; + +const extractTokenSymbolTemplate = `Given the recent message below: +{{recentMessages}} +Extract the 1 latest information about the requested token report: +- Input token symbol +- Extra about this symbol +When the symbol is specified in all lowered case, such as btc, eth, sol..., we should convert it into wellknown symbol. +E.g. btc instead of BTC, sol instead of SOL. +But when we see them in mixed form, such as SOl, DOl, eTH, except the case they're quoted (e.g. 'wEth', 'SOl',...) +When in doubt, specify the concern in the message field, include your suggested value with it. +Respond exactly a JSON object containing only the extracted values, no extra description or message needed. +Use null for any values that cannot be determined. The result should be a valid JSON object with the following schema: +{ + "symbol": string | null, + "message": string | null, +} +Examples: + Message: 'Tell me about BTC' + Response: '{ "symbol": "BTC", "message": null}' + Message: 'Do you know about SOl.' + Response: '{ "symbol": "SOl", "message": "We've found SOL seems match, is that what you want?"}' +`; + +const formatTokenReport = (data) => { + let output = `*Token Security and Trade Report*\n`; + output += `Token symbol: ${data.symbol}\n`; + output += `Token Address: ${data.tokenAddress}\n\n`; + + output += `*Ownership Distribution:*\n`; + output += `- Owner Balance: ${data.security.ownerBalance}\n`; + output += `- Creator Balance: ${data.security.creatorBalance}\n`; + output += `- Owner Percentage: ${data.security.ownerPercentage}%\n`; + output += `- Creator Percentage: ${data.security.creatorPercentage}%\n`; + output += `- Top 10 Holders Balance: ${data.security.top10HolderBalance}\n`; + output += `- Top 10 Holders Percentage: ${data.security.top10HolderPercent}%\n\n`; + + // Trade Data + output += `*Trade Data:*\n`; + output += `- Holders: ${data.volume.holder}\n`; + output += `- Unique Wallets (24h): ${data.volume.unique_wallet_24h}\n`; + output += `- Price Change (24h): ${data.volume.price_change_24h_percent}%\n`; + output += `- Price Change (12h): ${data.volume.price_change_12h_percent}%\n`; + output += `- Volume (24h USD): $${data.volume.volume_24h_usd}\n`; + output += `- Current Price: $${data.volume.price}\n\n`; + + return output; +}; + +const extractTokenSymbol = async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + options: any, + callback?: any +) => { + const context = composeContext({ + state, + template: extractTokenSymbolTemplate, + }); + + const response = await generateText({ + runtime, + context, + modelClass: ModelClass.LARGE, + }); + + elizaLogger.log("Response", response); + + try { + const regex = new RegExp(/\{(.+)\}/gms); + const normalized = response && regex.exec(response)?.[0]; + elizaLogger.debug("Normalized data", normalized); + return normalized && JSON.parse(normalized); + } catch { + callback?.({ text: response }); + return true; + } +}; + +export const reportToken = { + name: "REPORT_TOKEN", + similes: ["CHECK_TOKEN", "REVIEW_TOKEN", "TOKEN_DETAILS"], + description: "Check background data for a given token", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + options: any, + callback?: any + ) => { + try { + const params = await extractTokenSymbol( + runtime, + message, + state, + options, + callback + ); + + elizaLogger.debug("Params", params); + + if (!params?.symbol) { + callback?.({ text: "I need a token symbol to begin" }); + return true; + } + + if (params?.message) { + // show concern message + callback?.({ text: `*Warning*: ${params.message}` }); + } + + const symbol = params?.symbol; + elizaLogger.log("Fetching birdeye data", symbol); + const provider = new BirdeyeProvider(runtime.cacheManager); + + const [tokenAddress, security, volume] = await Promise.all([ + provider.getTokenAddress(symbol), + provider.fetchTokenSecurityBySymbol(symbol), + provider.fetchTokenTradeDataBySymbol(symbol), + ]); + + elizaLogger.log("Fetching birdeye done"); + const msg = formatTokenReport({ + symbol, + tokenAddress, + security: security.data, + volume: volume.data, + }); + callback?.({ text: msg }); + return true; + } catch (error) { + console.error("Error in reportToken handler:", error.message); + callback?.({ text: `Error: ${error.message}` }); + return false; + } + }, + validate: async (runtime: IAgentRuntime, message: Memory) => { + // todo: validate the token symbol + // for example, this action should not be triggered when the message is a wallet address + return true; + }, + examples: [ + [ + { + user: "user", + content: { + text: "Tell me what you know about SOL", + action: "CHECK_TOKEN", + }, + }, + { + user: "user", + content: { + text: "Do you know about SOL", + action: "TOKEN_DETAILS", + }, + }, + { + user: "user", + content: { + text: "Tell me about WETH", + action: "REVIEW_TOKEN", + }, + }, + ], + ] as ActionExample[][], +} as Action; diff --git a/packages/plugin-birdeye/src/index.ts b/packages/plugin-birdeye/src/index.ts index e997bfafb3..d4a835c48d 100644 --- a/packages/plugin-birdeye/src/index.ts +++ b/packages/plugin-birdeye/src/index.ts @@ -1,11 +1,14 @@ import { Plugin } from "@elizaos/core"; import { getSupportedNetworksAction } from "./actions/defi/networks"; +import { reportToken } from "./actions/report"; import { addressSearchProvider } from "./providers/address-search-provider"; +import { birdeyeProvider } from "./providers/birdeye"; export const birdeyePlugin: Plugin = { name: "birdeye", description: "Birdeye Plugin for token data and analytics", actions: [ + reportToken, getSupportedNetworksAction, // getTokenMetadataAction, // getPriceHistoryAction, @@ -13,7 +16,7 @@ export const birdeyePlugin: Plugin = { // getTokenTradesAction, ], evaluators: [], - providers: [addressSearchProvider], + providers: [addressSearchProvider, birdeyeProvider], }; export default birdeyePlugin; diff --git a/packages/plugin-birdeye/src/providers/birdeye.ts b/packages/plugin-birdeye/src/providers/birdeye.ts new file mode 100644 index 0000000000..e62cf22c9e --- /dev/null +++ b/packages/plugin-birdeye/src/providers/birdeye.ts @@ -0,0 +1,324 @@ +import { + elizaLogger, + IAgentRuntime, + ICacheManager, + Memory, + Provider, + settings, + State, +} from "@elizaos/core"; +import NodeCache from "node-cache"; +import * as path from "path"; +import { SearchTokensOptions } from "../types/search-token"; +import { BirdeyeChain } from "../types/shared"; +import { WalletDataOptions } from "../types/wallet"; + +const DEFAULT_MAX_RETRIES = 3; + +const DEFAULT_SUPPORTED_SYMBOLS = { + SOL: "So11111111111111111111111111111111111111112", + BTC: "qfnqNqs3nCAHjnyCgLRDbBtq4p2MtHZxw8YjSyYhPoL", + ETH: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + Example: "2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh", +}; + +const API_BASE_URL = "https://public-api.birdeye.so"; +const ENDPOINT_MAP = { + price: "/defi/price?address=", + security: "/defi/token_security?address=", + volume: "/defi/v3/token/trade-data/single?address=", + portfolio: "/v1/wallet/token_list?wallet=", + tokens: "/defi/tokenlist", +}; + +const RETRY_DELAY_MS = 2_000; + +const waitFor = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); + +class BaseCachedProvider { + private cache: NodeCache; + + constructor( + private cacheManager: ICacheManager, + private cacheKey, + ttl?: number + ) { + this.cache = new NodeCache({ stdTTL: ttl || 300 }); + } + + private readFsCache(key: string): Promise { + return this.cacheManager.get(path.join(this.cacheKey, key)); + } + + private writeFsCache(key: string, data: T): Promise { + return this.cacheManager.set(path.join(this.cacheKey, key), data, { + expires: Date.now() + 5 * 60 * 1000, + }); + } + + public async readFromCache(key: string): Promise { + // get memory cache first + const val = this.cache.get(key); + if (val) { + return val; + } + + const fsVal = await this.readFsCache(key); + if (fsVal) { + // set to memory cache + this.cache.set(key, fsVal); + } + + return fsVal; + } + + public async writeToCache(key: string, val: T): Promise { + // Set in-memory cache + this.cache.set(key, val); + + // Write to file-based cache + await this.writeFsCache(key, val); + } +} + +export class BirdeyeProvider extends BaseCachedProvider { + private symbolMap: Record; + private maxRetries: number; + + constructor( + cacheManager: ICacheManager, + symbolMap?: Record, + maxRetries?: number + ) { + super(cacheManager, "birdeye/data"); + this.symbolMap = symbolMap || DEFAULT_SUPPORTED_SYMBOLS; + this.maxRetries = maxRetries || DEFAULT_MAX_RETRIES; + } + + public getTokenAddress(symbol: string) { + const addr = this.symbolMap[symbol]; + + if (!addr) { + throw new Error(`Unsupported symbol ${symbol} in Birdeye provider`); + } + + return addr; + } + + private getUrlByType(type: string, address?: string) { + const path = ENDPOINT_MAP[type]; + + if (!path) { + throw new Error(`Unsupported symbol ${type} in Birdeye provider`); + } + + return `${API_BASE_URL}${path}${address || ""}`; + } + + private async fetchWithRetry( + url: string, + options: RequestInit = {} + ): Promise { + let attempts = 0; + + while (attempts < this.maxRetries) { + attempts++; + try { + const resp = await fetch(url, { + ...options, + headers: { + Accept: "application/json", + "x-chain": settings.BIRDEYE_CHAIN || "solana", + "X-API-KEY": settings.BIRDEYE_API_KEY || "", + ...options.headers, + }, + }); + + if (!resp.ok) { + const errorText = await resp.text(); + throw new Error( + `HTTP error! status: ${resp.status}, message: ${errorText}` + ); + } + + const data = await resp.json(); + return data; + } catch (error) { + if (attempts === this.maxRetries) { + // failed after all + throw error; + } + await waitFor(RETRY_DELAY_MS); + } + } + } + + private async fetchWithCacheAndRetry( + type: string, + address?: string + ): Promise { + const key = `${type}/${address}`; + const val = await this.readFromCache(key); + + if (val) { + return val; + } + + const url = this.getUrlByType(type, address); + const data = await this.fetchWithRetry(url); + + await this.writeToCache(key, data); + return data; + } + + public async fetchTokenList() { + return this.fetchWithCacheAndRetry("tokens"); + } + + public async fetchPriceBySymbol(symbol: string) { + return this.fetchPriceByAddress(this.getTokenAddress(symbol)); + } + public async fetchPriceByAddress(address: string) { + return this.fetchWithCacheAndRetry("price", address); + } + + public async fetchTokenSecurityBySymbol(symbol: string) { + return this.fetchTokenSecurityByAddress(this.getTokenAddress(symbol)); + } + public async fetchTokenSecurityByAddress(address: string) { + return this.fetchWithCacheAndRetry("security", address); + } + + public async fetchTokenTradeDataBySymbol(symbol: string) { + return this.fetchTokenTradeDataByAddress(this.getTokenAddress(symbol)); + } + public async fetchTokenTradeDataByAddress(address: string) { + return this.fetchWithCacheAndRetry("volume", address); + } + + public async fetchWalletPortfolio(address: string) { + return this.fetchWithCacheAndRetry("portfolio", address); + } + + public async fetchSearchTokens(options: SearchTokensOptions) { + const { keyword, chain = "all", limit = 1, offset = 0, type } = options; + const params = new URLSearchParams({ + keyword, + limit: limit.toString(), + offset: offset.toString(), + chain: chain, + }); + + const url = `${API_BASE_URL}/defi/v3/search?${params.toString()}`; + const data = await this.fetchWithRetry(url); + + return type === "address" + ? data.data.items + .filter( + (item) => + item.type === "token" && + item.result[0].address === keyword.toLowerCase() + ) + .flatMap((item) => item.result) + : data.data.items + .filter( + (item) => + item.type === "token" && + item.result[0].symbol === keyword + ) + .flatMap((item) => item.result); + } + + public async fetchSearchWallets(options: WalletDataOptions) { + const { wallet, chain = "solana" } = options; + const params = new URLSearchParams({ + wallet, + chain: chain, + }); + + const url = `${API_BASE_URL}/v1/wallet/token_list?${params.toString()}`; + const data = await this.fetchWithRetry(url, { + headers: { "x-chain": chain }, + }); + + return data.data.items; + } + + public async fetchTokenMetadata(address: string, chain: BirdeyeChain) { + const isValidAddress = (() => { + switch (chain) { + case "solana": + return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address); + case "sui": + return /^0x[a-fA-F0-9]{64}$/i.test(address); + case "ethereum": + case "arbitrum": + case "avalanche": + case "bsc": + case "optimism": + case "polygon": + case "base": + case "zksync": + return /^0x[a-fA-F0-9]{40}$/i.test(address); + default: + return false; + } + })(); + + if (!isValidAddress) { + elizaLogger.error( + `Invalid address format for ${chain}: ${address}` + ); + return null; + } + + const params = new URLSearchParams({ address }); + const url = `${API_BASE_URL}/defi/v3/token/meta-data/single?${params.toString()}`; + + return this.fetchWithRetry(url, { + headers: { "x-chain": chain }, + }).catch(() => null); + } +} + +export const birdeyeProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + _message: Memory, + _state?: State + ): Promise => { + try { + const provider = new BirdeyeProvider(runtime.cacheManager); + + const walletAddr = runtime.getSetting("BIRDEYE_WALLET_ADDR"); + + const resp = await provider.fetchTokenList().catch((err) => { + elizaLogger.warn("Couldn't update symbol map", err); + }); + + resp?.data?.tokens?.forEach((item) => { + DEFAULT_SUPPORTED_SYMBOLS[item.symbol] = item.address; + }); + + const supportedTokens = Object.keys(DEFAULT_SUPPORTED_SYMBOLS).join( + ", " + ); + + if (!walletAddr) { + console.warn("No Birdeye wallet was specified"); + + return `Birdeye enabled, no wallet found, supported tokens: [${supportedTokens}]`; + } + const response = await provider.fetchWalletPortfolio(walletAddr); + const portfolio = response?.data.items + .map((e) => e.symbol) + .join(", "); + + return `Birdeye enabled, wallet addr: ${walletAddr}, portfolio: [${portfolio}], supported tokens: [${supportedTokens}]`; + } catch (error) { + console.error("Error fetching token data:", error); + return "Unable to fetch token information. Please try again later."; + } + }, +}; diff --git a/packages/plugin-birdeye/src/tests/birdeye.test.ts b/packages/plugin-birdeye/src/tests/birdeye.test.ts new file mode 100644 index 0000000000..7021926cb3 --- /dev/null +++ b/packages/plugin-birdeye/src/tests/birdeye.test.ts @@ -0,0 +1,292 @@ +import { ICacheManager } from "@elizaos/core"; +import NodeCache from "node-cache"; +import { BirdeyeProvider } from "../providers/birdeye"; + +import { afterEach, beforeEach, describe, expect, it, Mock, vi } from "vitest"; + +describe("BirdeyeProvider", () => { + describe("basic fetching", () => { + let cacheManager: ICacheManager; + let provider: BirdeyeProvider; + + beforeEach(() => { + cacheManager = { + get: vi.fn(), + set: vi.fn(), + } as unknown as ICacheManager; + provider = new BirdeyeProvider(cacheManager); + global.fetch = vi.fn(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should fetch price by symbol", async () => { + const mockResponse = { price: 100 }; + (fetch as Mock).mockResolvedValue({ + ok: true, + json: async () => mockResponse, + }); + + const result = await provider.fetchPriceBySymbol("SOL"); + + expect(result).toEqual(mockResponse); + expect(fetch).toHaveBeenCalledWith( + "https://public-api.birdeye.so/defi/price?address=So11111111111111111111111111111111111111112", + expect.any(Object) + ); + }); + + it("should fetch token security by symbol", async () => { + const mockResponse = { security: "secure" }; + (fetch as Mock).mockResolvedValue({ + ok: true, + json: async () => mockResponse, + }); + + const result = await provider.fetchTokenSecurityBySymbol("SOL"); + + expect(result).toEqual(mockResponse); + expect(fetch).toHaveBeenCalledWith( + "https://public-api.birdeye.so/defi/token_security?address=So11111111111111111111111111111111111111112", + expect.any(Object) + ); + }); + + it("should fetch token trade data by symbol", async () => { + const mockResponse = { volume: 1000 }; + (fetch as Mock).mockResolvedValue({ + ok: true, + json: async () => mockResponse, + }); + + const result = await provider.fetchTokenTradeDataBySymbol("SOL"); + + expect(result).toEqual(mockResponse); + expect(fetch).toHaveBeenCalledWith( + "https://public-api.birdeye.so/defi/v3/token/trade-data/single?address=So11111111111111111111111111111111111111112", + expect.any(Object) + ); + }); + + it("should fetch wallet portfolio", async () => { + const mockResponse = { portfolio: [] }; + (fetch as Mock).mockResolvedValue({ + ok: true, + json: async () => mockResponse, + }); + + const result = await provider.fetchWalletPortfolio( + "some-wallet-address" + ); + + expect(result).toEqual(mockResponse); + expect(fetch).toHaveBeenCalledWith( + "https://public-api.birdeye.so/v1/wallet/token_list?wallet=some-wallet-address", + expect.any(Object) + ); + }); + }); + + describe("retries options", () => { + let cacheManager: ICacheManager; + let provider: BirdeyeProvider; + + beforeEach(() => { + cacheManager = { + get: vi.fn(), + set: vi.fn(), + } as unknown as ICacheManager; + provider = new BirdeyeProvider(cacheManager); + global.fetch = vi.fn(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should retry fetch on failure and succeed", async () => { + const mockResponse = { price: 100 }; + (fetch as Mock) + .mockRejectedValueOnce(new Error("Network error")) // First attempt fails + .mockResolvedValueOnce({ + ok: true, + json: async () => mockResponse, + }); // Second attempt succeeds + + const result = await provider.fetchPriceBySymbol("SOL"); + + expect(result).toEqual(mockResponse); + expect(fetch).toHaveBeenCalledTimes(2); // Ensure it retried + }); + + it("should fail after max retries", async () => { + const error = new Error("Network error"); + (fetch as Mock) + .mockRejectedValueOnce(error) + .mockRejectedValueOnce(error) // Second attempt fails + .mockRejectedValueOnce(error); // Third attempt also fails + + await expect(provider.fetchPriceBySymbol("SOL")).rejects.toThrow( + "Network error" + ); + + expect(fetch).toHaveBeenCalledTimes(3); // Ensure it retried + }); + }); + + describe("with custom symbols", () => { + let cacheManager: ICacheManager; + let provider: BirdeyeProvider; + + beforeEach(() => { + cacheManager = { + get: vi.fn(), + set: vi.fn(), + } as unknown as ICacheManager; + provider = new BirdeyeProvider(cacheManager, { + WETH: "0x32323232323232", + }); + global.fetch = vi.fn(); + }); + + it("should fetch price for a custom symbol WETH", async () => { + const mockResponse = { price: 2000 }; + (fetch as Mock).mockResolvedValue({ + ok: true, + json: async () => mockResponse, + }); + + const result = await provider.fetchPriceBySymbol("WETH"); + + expect(result).toEqual(mockResponse); + expect(fetch).toHaveBeenCalledWith( + "https://public-api.birdeye.so/defi/price?address=0x32323232323232", + expect.any(Object) + ); + }); + + it("should fetch token security for a custom symbol WETH", async () => { + const mockResponse = { security: "secure" }; + (fetch as Mock).mockResolvedValue({ + ok: true, + json: async () => mockResponse, + }); + + const result = await provider.fetchTokenSecurityBySymbol("WETH"); + + expect(result).toEqual(mockResponse); + expect(fetch).toHaveBeenCalledWith( + "https://public-api.birdeye.so/defi/token_security?address=0x32323232323232", + expect.any(Object) + ); + }); + + it("should fetch token trade data for a custom symbol WETH", async () => { + const mockResponse = { volume: 500 }; + (fetch as Mock).mockResolvedValue({ + ok: true, + json: async () => mockResponse, + }); + + const result = await provider.fetchTokenTradeDataBySymbol("WETH"); + + expect(result).toEqual(mockResponse); + expect(fetch).toHaveBeenCalledWith( + "https://public-api.birdeye.so/defi/v3/token/trade-data/single?address=0x32323232323232", + expect.any(Object) + ); + }); + }); + + describe("with cache", () => { + let cacheManager: ICacheManager; + let provider: BirdeyeProvider; + let nodeCache: NodeCache; + + beforeEach(() => { + nodeCache = new NodeCache(); + cacheManager = { + get: vi.fn(), + set: vi.fn(), + } as unknown as ICacheManager; + + provider = new BirdeyeProvider(cacheManager); + provider["cache"] = nodeCache; // Directly set the node cache + global.fetch = vi.fn(); + }); + + afterEach(() => { + vi.clearAllMocks(); + nodeCache.flushAll(); + }); + + it("should use memory cache when fetching price by symbol", async () => { + const mockResponse = { price: 100 }; + nodeCache.set( + "price/So11111111111111111111111111111111111111112", + mockResponse + ); // Pre-fill cache + + const result = await provider.fetchPriceBySymbol("SOL"); + + expect(result).toEqual(mockResponse); + expect(fetch).not.toHaveBeenCalled(); + }); + + it("should fetch and cache price by symbol", async () => { + const mockResponse = { price: 100 }; + (fetch as Mock).mockResolvedValue({ + ok: true, + json: async () => mockResponse, + }); + + // First call - should fetch and cache the result + const result1 = await provider.fetchPriceBySymbol("SOL"); + expect(result1).toEqual(mockResponse); + expect(fetch).toHaveBeenCalledTimes(1); + + // Second call - should use cache + const result2 = await provider.fetchPriceBySymbol("SOL"); + expect(result2).toEqual(mockResponse); + expect(fetch).toHaveBeenCalledTimes(1); // No additional fetch call + }); + + it("should use file system cache when fetching price by symbol", async () => { + const mockResponse = { price: 100 }; + (cacheManager.get as Mock).mockResolvedValue(mockResponse); + + // Memory cache miss, should use file system cache + const result = await provider.fetchPriceBySymbol("SOL"); + expect(result).toEqual(mockResponse); + expect(fetch).not.toHaveBeenCalled(); + }); + + it("should fetch and cache price by symbol using file system cache", async () => { + const mockResponse = { price: 100 }; + (fetch as Mock).mockResolvedValue({ + ok: true, + json: async () => mockResponse, + }); + (cacheManager.get as Mock).mockResolvedValue(null); // File system cache miss + + // First call - should fetch and cache the result + const result1 = await provider.fetchPriceBySymbol("SOL"); + expect(result1).toEqual(mockResponse); + expect(fetch).toHaveBeenCalledTimes(1); + expect(cacheManager.set).toHaveBeenCalledWith( + expect.stringContaining( + "birdeye/data/price/So11111111111111111111111111111111111111112" + ), + mockResponse, + expect.any(Object) + ); + + // Second call - should use cache + const result2 = await provider.fetchPriceBySymbol("SOL"); + expect(result2).toEqual(mockResponse); + expect(fetch).toHaveBeenCalledTimes(1); // No additional fetch call + }); + }); +}); From 8025000e5ca2dbd5531ff55cee2ad7e224b9615a Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Mon, 30 Dec 2024 15:00:29 -0800 Subject: [PATCH 06/33] chore: work in progress changes --- .vscode/launch.json | 43 ++- packages/plugin-birdeye/.nvmrc | 1 + packages/plugin-birdeye/README.md | 68 ++--- packages/plugin-birdeye/package.json | 8 +- .../src/actions/defi/get-ohlcv.ts | 4 +- .../src/actions/defi/get-price-history.ts | 4 +- .../src/actions/defi/get-token-metadata.ts | 4 +- packages/plugin-birdeye/src/actions/report.ts | 4 +- .../src/{providers => }/birdeye.ts | 156 ++++------ packages/plugin-birdeye/src/constants.ts | 19 ++ packages/plugin-birdeye/src/index.ts | 11 +- .../src/providers/address-search-provider.ts | 81 ++--- .../src/providers/agent-portfolio-provider.ts | 46 +++ .../src/providers/symbol-search-provider.ts | 87 ++++++ .../providers/wallet-portfolio-provider.ts | 75 +++++ packages/plugin-birdeye/src/services.ts | 174 ----------- .../plugin-birdeye/src/tests/birdeye.test.ts | 2 +- .../plugin-birdeye/src/types/search-token.ts | 4 +- .../plugin-birdeye/src/types/token-list-v1.ts | 30 ++ .../src/types/token-metadata.ts | 31 +- packages/plugin-birdeye/src/types/wallet.ts | 6 +- packages/plugin-birdeye/src/utils.ts | 84 +++--- pnpm-lock.yaml | 285 ++++++++++++++++-- 23 files changed, 748 insertions(+), 479 deletions(-) create mode 100644 packages/plugin-birdeye/.nvmrc rename packages/plugin-birdeye/src/{providers => }/birdeye.ts (64%) create mode 100644 packages/plugin-birdeye/src/constants.ts create mode 100644 packages/plugin-birdeye/src/providers/agent-portfolio-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/symbol-search-provider.ts create mode 100644 packages/plugin-birdeye/src/providers/wallet-portfolio-provider.ts delete mode 100644 packages/plugin-birdeye/src/services.ts create mode 100644 packages/plugin-birdeye/src/types/token-list-v1.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 30a1891bdc..57b7269fe5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,8 +9,45 @@ "request": "launch", "name": "Launch via pnpm", "runtimeExecutable": "pnpm", - "runtimeArgs": ["run", "dev"], - "skipFiles": ["/**"] + "runtimeVersion": "23.3.0", + "runtimeArgs": [ + "run", + "dev" + ], + "skipFiles": [ + "/**" + ] + }, + { + "name": "Current TS File", + "type": "node", + "request": "launch", + "args": [ + "${relativeFile}", + "-p", + "${workspaceFolder}/tsconfig.json" + ], + "runtimeVersion": "23.3.0", + "runtimeArgs": [ + "-r", + "ts-node/register", + "-r", + "tsconfig-paths/register", + "--loader", + "ts-node/esm", + "--nolazy" + ], + "cwd": "${workspaceRoot}", + "internalConsoleOptions": "openOnSessionStart", + "envFile": "${workspaceFolder}/.env", + "smartStep": true, + "skipFiles": [ + "/**", + "node_modules/**" + ], + "env": { + "TS_NODE_COMPILER_OPTIONS": "{\"module\": \"commonjs\"}" + } } ] -} +} \ No newline at end of file diff --git a/packages/plugin-birdeye/.nvmrc b/packages/plugin-birdeye/.nvmrc new file mode 100644 index 0000000000..fa12cf298e --- /dev/null +++ b/packages/plugin-birdeye/.nvmrc @@ -0,0 +1 @@ +v23.3.0 \ No newline at end of file diff --git a/packages/plugin-birdeye/README.md b/packages/plugin-birdeye/README.md index 3da190d956..9b0d146780 100644 --- a/packages/plugin-birdeye/README.md +++ b/packages/plugin-birdeye/README.md @@ -4,74 +4,40 @@ A powerful plugin for Eliza that integrates with Birdeye's comprehensive DeFi an ## Features -- **DeFi Analytics** +### Provider Featurs - - Real-time price and trading data - - Historical price tracking - - OHLCV (Open, High, Low, Close, Volume) data - - Trade analysis for tokens and pairs +- **Wallet Portfolio Provider** -- **Token Intelligence** + - If `BIRDEYE_WALLET_ADDR` is set, this provider will fetch the wallet's portfolio data from Birdeye and be able to respond to questions - - Comprehensive token metadata - - Security information - - Token holder analytics - - Mint and burn tracking - - Market trends and new listings +- **Wallet Search Provider** -- **Wallet Analysis** + - If the user mentions a wallet address, this provider will search for the address in Birdeye and be able to provide information about the wallet. This includes support for multiple addresses in the same message. - - Multi-chain portfolio tracking - - Token balance monitoring - - Transaction history analysis - - Cross-chain analytics +- **Symbol Search Provider** -- **Market Research** - - Gainers and losers tracking - - Trending tokens - - Top trader analysis - - Market pair analytics + - If the user mentions a token symbol such as $SOL, $ETH or any random token symbol, this provider will search for the symbol in Birdeye and be able to provide information about the token. This includes support for multiple symbols in the same message. + - i.e. "Tell me about $SOL and $PEPE" -## Installation +- **Address Search Provider** -```bash -npm install @eliza/plugin-birdeye -``` + - If the user mentions a token address, this provider will search for the address in Birdeye and be able to provide information about the token. This includes support for multiple addresses in the same message. + - i.e. "Tell me about 0x1234567890 and 0x9876543210" -## Configuration +### Action Features -Add the following to your Eliza configuration: +- **Report Token** -```typescript -import { BirdeyePlugin } from "@eliza/plugin-birdeye"; - -export default { - plugins: [ - new BirdeyePlugin({ - apiKey: "YOUR_BIRDEYE_API_KEY", - }), - ], -}; -``` - -## Environment Variables - -``` -BIRDEYE_API_KEY=your_api_key_here -``` - -## Usage - -Once configured, the plugin provides access to Birdeye data through Eliza's interface. + - This action will report on the current details of the wallet specified in the `BIRDEYE_WALLET_ADDR` setting. ## API Reference -The plugin provides access to all Birdeye API endpoints through structured interfaces. For detailed API documentation, visit [Birdeye's API Documentation](https://public-api.birdeye.so). +The plugin provides access to a subset of Birdeye API endpoints through structured interfaces. For detailed API documentation, visit [Birdeye's API Documentation](https://public-api.birdeye.so). ## License -MIT +See parent project for license information. ## Contributing -Contributions are welcome! Please read our contributing guidelines before submitting pull requests. +Contributions are welcome! See parent project for contribution guidelines. diff --git a/packages/plugin-birdeye/package.json b/packages/plugin-birdeye/package.json index 4e4edb3fb9..682b88c751 100644 --- a/packages/plugin-birdeye/package.json +++ b/packages/plugin-birdeye/package.json @@ -5,8 +5,8 @@ "type": "module", "types": "dist/index.d.ts", "dependencies": { - "@elizaos/core": "workspace:*", "@coral-xyz/anchor": "0.30.1", + "@elizaos/core": "workspace:*", "@solana/spl-token": "0.4.9", "@solana/web3.js": "1.95.8", "bignumber": "1.1.0", @@ -27,5 +27,11 @@ "peerDependencies": { "form-data": "4.0.1", "whatwg-url": "7.1.0" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.7.2" } } \ No newline at end of file diff --git a/packages/plugin-birdeye/src/actions/defi/get-ohlcv.ts b/packages/plugin-birdeye/src/actions/defi/get-ohlcv.ts index 7300cc224f..6c2b50b8cb 100644 --- a/packages/plugin-birdeye/src/actions/defi/get-ohlcv.ts +++ b/packages/plugin-birdeye/src/actions/defi/get-ohlcv.ts @@ -14,8 +14,8 @@ import { BirdeyeChain } from "../../types/shared"; import { TokenMetadataResponse } from "../../types/token-metadata"; import { BASE_URL, + extractAddressesFromString, extractChain, - extractContractAddresses, formatTimestamp, formatValue, makeApiRequest, @@ -293,7 +293,7 @@ export const getOHLCVAction: Action = { } const messageText = message.content.text; - const addresses = extractContractAddresses(messageText); + const addresses = extractAddressesFromString(messageText); if (addresses.length === 0) { callbackData.text = "I couldn't find a valid token address in your message."; diff --git a/packages/plugin-birdeye/src/actions/defi/get-price-history.ts b/packages/plugin-birdeye/src/actions/defi/get-price-history.ts index 60ae76f0a0..3e501a6904 100644 --- a/packages/plugin-birdeye/src/actions/defi/get-price-history.ts +++ b/packages/plugin-birdeye/src/actions/defi/get-price-history.ts @@ -14,8 +14,8 @@ import { BirdeyeChain } from "../../types/shared"; import { TokenMetadataResponse } from "../../types/token-metadata"; import { BASE_URL, + extractAddressesFromString, extractChain, - extractContractAddresses, extractTimeRange, formatTimestamp, formatValue, @@ -331,7 +331,7 @@ export const getPriceHistoryAction: Action = { } const messageText = message.content.text; - const addresses = extractContractAddresses(messageText); + const addresses = extractAddressesFromString(messageText); if (addresses.length === 0) { callbackData.text = "I couldn't find a valid token address in your message."; diff --git a/packages/plugin-birdeye/src/actions/defi/get-token-metadata.ts b/packages/plugin-birdeye/src/actions/defi/get-token-metadata.ts index cd8cca6815..bc76b9358d 100644 --- a/packages/plugin-birdeye/src/actions/defi/get-token-metadata.ts +++ b/packages/plugin-birdeye/src/actions/defi/get-token-metadata.ts @@ -13,8 +13,8 @@ import { BirdeyeChain } from "../../types/shared"; import { CHAIN_ALIASES, CHAIN_KEYWORDS, + extractAddressesFromString, extractChain, - extractContractAddresses, } from "../../utils"; // Constants for keyword matching @@ -89,7 +89,7 @@ export const getTokenMetadataAction: Action = { } const messageText = message.content.text; - const addresses = extractContractAddresses(messageText); + const addresses = extractAddressesFromString(messageText); const chain = extractChain(messageText); // Check if a specific chain was mentioned (including aliases) diff --git a/packages/plugin-birdeye/src/actions/report.ts b/packages/plugin-birdeye/src/actions/report.ts index abb34dddb5..331a3e68af 100644 --- a/packages/plugin-birdeye/src/actions/report.ts +++ b/packages/plugin-birdeye/src/actions/report.ts @@ -9,7 +9,7 @@ import { type Memory, type State, } from "@elizaos/core"; -import { BirdeyeProvider } from "../providers/birdeye"; +import { BirdeyeProvider } from "../birdeye"; const extractTokenSymbolTemplate = `Given the recent message below: {{recentMessages}} @@ -146,7 +146,7 @@ export const reportToken = { return false; } }, - validate: async (runtime: IAgentRuntime, message: Memory) => { + validate: async (_runtime: IAgentRuntime, _message: Memory) => { // todo: validate the token symbol // for example, this action should not be triggered when the message is a wallet address return true; diff --git a/packages/plugin-birdeye/src/providers/birdeye.ts b/packages/plugin-birdeye/src/birdeye.ts similarity index 64% rename from packages/plugin-birdeye/src/providers/birdeye.ts rename to packages/plugin-birdeye/src/birdeye.ts index e62cf22c9e..6c34331cf1 100644 --- a/packages/plugin-birdeye/src/providers/birdeye.ts +++ b/packages/plugin-birdeye/src/birdeye.ts @@ -1,40 +1,18 @@ -import { - elizaLogger, - IAgentRuntime, - ICacheManager, - Memory, - Provider, - settings, - State, -} from "@elizaos/core"; +import { elizaLogger, ICacheManager, settings } from "@elizaos/core"; import NodeCache from "node-cache"; import * as path from "path"; -import { SearchTokensOptions } from "../types/search-token"; -import { BirdeyeChain } from "../types/shared"; -import { WalletDataOptions } from "../types/wallet"; - -const DEFAULT_MAX_RETRIES = 3; - -const DEFAULT_SUPPORTED_SYMBOLS = { - SOL: "So11111111111111111111111111111111111111112", - BTC: "qfnqNqs3nCAHjnyCgLRDbBtq4p2MtHZxw8YjSyYhPoL", - ETH: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", - Example: "2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh", -}; - -const API_BASE_URL = "https://public-api.birdeye.so"; -const ENDPOINT_MAP = { - price: "/defi/price?address=", - security: "/defi/token_security?address=", - volume: "/defi/v3/token/trade-data/single?address=", - portfolio: "/v1/wallet/token_list?wallet=", - tokens: "/defi/tokenlist", -}; - -const RETRY_DELAY_MS = 2_000; - -const waitFor = (ms: number) => - new Promise((resolve) => setTimeout(resolve, ms)); +import { + API_BASE_URL, + DEFAULT_MAX_RETRIES, + DEFAULT_SUPPORTED_SYMBOLS, + ENDPOINT_MAP, + RETRY_DELAY_MS, +} from "./constants"; +import { SearchTokenResponse, SearchTokensOptions } from "./types/search-token"; +import { BirdeyeChain } from "./types/shared"; +import { TokenMetadataResponse } from "./types/token-metadata"; +import { WalletDataOptions, WalletDataResponse } from "./types/wallet"; +import { waitFor } from "./utils"; class BaseCachedProvider { private cache: NodeCache; @@ -201,8 +179,15 @@ export class BirdeyeProvider extends BaseCachedProvider { return this.fetchWithCacheAndRetry("portfolio", address); } - public async fetchSearchTokens(options: SearchTokensOptions) { - const { keyword, chain = "all", limit = 1, offset = 0, type } = options; + /** + * Fetches token data for a given keyword and chain. + * @param options - The options for the token data. + * @returns The token data. + */ + public async fetchSearchTokens( + options: SearchTokensOptions + ): Promise { + const { keyword, chain = "all", limit = 1, offset = 0 } = options; const params = new URLSearchParams({ keyword, limit: limit.toString(), @@ -211,41 +196,42 @@ export class BirdeyeProvider extends BaseCachedProvider { }); const url = `${API_BASE_URL}/defi/v3/search?${params.toString()}`; - const data = await this.fetchWithRetry(url); - - return type === "address" - ? data.data.items - .filter( - (item) => - item.type === "token" && - item.result[0].address === keyword.toLowerCase() - ) - .flatMap((item) => item.result) - : data.data.items - .filter( - (item) => - item.type === "token" && - item.result[0].symbol === keyword - ) - .flatMap((item) => item.result); + const response: SearchTokenResponse = await this.fetchWithRetry(url); + return response; } - public async fetchSearchWallets(options: WalletDataOptions) { - const { wallet, chain = "solana" } = options; + /** + * Fetches wallet portfolio data for a given wallet address and chains. + * @param options - The options for the wallet portfolio data. + * @returns The wallet portfolio data. + */ + public async fetchSearchWallets( + options: WalletDataOptions + ): Promise { + const { wallet, chain } = options; + const params = new URLSearchParams({ wallet, - chain: chain, }); const url = `${API_BASE_URL}/v1/wallet/token_list?${params.toString()}`; - const data = await this.fetchWithRetry(url, { + const response = await this.fetchWithRetry(url, { headers: { "x-chain": chain }, }); - return data.data.items; + return response; } - public async fetchTokenMetadata(address: string, chain: BirdeyeChain) { + /** + * Fetches token metadata for a given address and chain. + * @param address - The address of the token. + * @param chain - The chain of the token. + * @returns The token metadata. + */ + public async fetchTokenMetadata( + address: string, + chain: BirdeyeChain + ): Promise { const isValidAddress = (() => { switch (chain) { case "solana": @@ -276,49 +262,17 @@ export class BirdeyeProvider extends BaseCachedProvider { const params = new URLSearchParams({ address }); const url = `${API_BASE_URL}/defi/v3/token/meta-data/single?${params.toString()}`; - return this.fetchWithRetry(url, { + const response: TokenMetadataResponse = await this.fetchWithRetry(url, { headers: { "x-chain": chain }, }).catch(() => null); - } -} -export const birdeyeProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - _message: Memory, - _state?: State - ): Promise => { - try { - const provider = new BirdeyeProvider(runtime.cacheManager); - - const walletAddr = runtime.getSetting("BIRDEYE_WALLET_ADDR"); - - const resp = await provider.fetchTokenList().catch((err) => { - elizaLogger.warn("Couldn't update symbol map", err); - }); - - resp?.data?.tokens?.forEach((item) => { - DEFAULT_SUPPORTED_SYMBOLS[item.symbol] = item.address; - }); - - const supportedTokens = Object.keys(DEFAULT_SUPPORTED_SYMBOLS).join( - ", " + if (!response) { + elizaLogger.error( + `Failed to fetch token metadata for ${address} on ${chain}` ); - - if (!walletAddr) { - console.warn("No Birdeye wallet was specified"); - - return `Birdeye enabled, no wallet found, supported tokens: [${supportedTokens}]`; - } - const response = await provider.fetchWalletPortfolio(walletAddr); - const portfolio = response?.data.items - .map((e) => e.symbol) - .join(", "); - - return `Birdeye enabled, wallet addr: ${walletAddr}, portfolio: [${portfolio}], supported tokens: [${supportedTokens}]`; - } catch (error) { - console.error("Error fetching token data:", error); - return "Unable to fetch token information. Please try again later."; + return null; } - }, -}; + + return response; + } +} diff --git a/packages/plugin-birdeye/src/constants.ts b/packages/plugin-birdeye/src/constants.ts new file mode 100644 index 0000000000..ff20e16d59 --- /dev/null +++ b/packages/plugin-birdeye/src/constants.ts @@ -0,0 +1,19 @@ +export const DEFAULT_MAX_RETRIES = 3; + +export const DEFAULT_SUPPORTED_SYMBOLS = { + SOL: "So11111111111111111111111111111111111111112", + BTC: "qfnqNqs3nCAHjnyCgLRDbBtq4p2MtHZxw8YjSyYhPoL", + ETH: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + Example: "2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh", +}; + +export const API_BASE_URL = "https://public-api.birdeye.so"; +export const ENDPOINT_MAP = { + price: "/defi/price?address=", + security: "/defi/token_security?address=", + volume: "/defi/v3/token/trade-data/single?address=", + portfolio: "/v1/wallet/token_list?wallet=", + tokens: "/defi/tokenlist", +}; + +export const RETRY_DELAY_MS = 2_000; diff --git a/packages/plugin-birdeye/src/index.ts b/packages/plugin-birdeye/src/index.ts index d4a835c48d..dee6e5882a 100644 --- a/packages/plugin-birdeye/src/index.ts +++ b/packages/plugin-birdeye/src/index.ts @@ -2,7 +2,9 @@ import { Plugin } from "@elizaos/core"; import { getSupportedNetworksAction } from "./actions/defi/networks"; import { reportToken } from "./actions/report"; import { addressSearchProvider } from "./providers/address-search-provider"; -import { birdeyeProvider } from "./providers/birdeye"; +import { agentPortfolioProvider } from "./providers/agent-portfolio-provider"; +import { symbolSearchProvider } from "./providers/symbol-search-provider"; +import { walletPortfolioProvider } from "./providers/wallet-portfolio-provider"; export const birdeyePlugin: Plugin = { name: "birdeye", @@ -16,7 +18,12 @@ export const birdeyePlugin: Plugin = { // getTokenTradesAction, ], evaluators: [], - providers: [addressSearchProvider, birdeyeProvider], + providers: [ + symbolSearchProvider, + addressSearchProvider, + walletPortfolioProvider, + agentPortfolioProvider, + ], }; export default birdeyePlugin; diff --git a/packages/plugin-birdeye/src/providers/address-search-provider.ts b/packages/plugin-birdeye/src/providers/address-search-provider.ts index 2f60ee08d6..d20c819ba5 100644 --- a/packages/plugin-birdeye/src/providers/address-search-provider.ts +++ b/packages/plugin-birdeye/src/providers/address-search-provider.ts @@ -5,16 +5,15 @@ import { Provider, State, } from "@elizaos/core"; -import { getTokenMetadata, searchTokens } from "../services"; +import { BirdeyeProvider } from "../birdeye"; import { + extractAddressesFromString, extractChain, - extractContractAddresses, - extractSymbols, formatTokenInfo, } from "../utils"; /** - * Searches message text for contract addresses, symbols, or wallet addresses and enriches them with: + * Searches message text for ALL contract addresses, symbols, or wallet addresses and enriches them with: * - Portfolio data if its a wallet address * - Token metadata if its a contract address or symbol */ @@ -24,91 +23,57 @@ export const addressSearchProvider: Provider = { message: Memory, _state?: State ): Promise => { - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - return null; - } + const provider = new BirdeyeProvider(runtime.cacheManager); const messageText = message.content.text; // STEP 1 - Extract addresses and symbols - const addresses = extractContractAddresses(messageText); - const symbols = extractSymbols(messageText); - - if (addresses.length === 0 && symbols.length === 0) return null; + const addresses = extractAddressesFromString(messageText); + if (addresses.length === 0) return null; elizaLogger.info( - `Searching Birdeye provider for ${addresses.length} addresses and ${symbols.length} symbols` + `Searching Birdeye provider for ${addresses.length} token addresses` ); // STEP 2 - Search Birdeye services for token matches based on addresses and symbols - - // Search in parallel for all terms const searchAddressesForTokenMatch = addresses.map((address) => - searchTokens(apiKey, { - keyword: address.address, - limit: 1, - }).then((results) => ({ - searchTerm: address.address, - address: address.address, - result: results[0] || null, - })) - ); - - // Search in parallel for all terms - const searchSymbolsForTokenMatch = symbols.map((symbol) => - searchTokens(apiKey, { - keyword: symbol, - limit: 1, - }).then((results) => ({ - searchTerm: symbol, - symbol: results[0]?.symbol || null, - address: results[0]?.address || null, - result: results[0] || null, - })) + provider + .fetchSearchTokens({ + keyword: address.address, + limit: 1, + }) + .then((results) => ({ + searchTerm: address.address, + address: address.address, + result: results[0] || null, + })) ); - const results = await Promise.all([ - ...searchAddressesForTokenMatch, - ...searchSymbolsForTokenMatch, - ]); + const results = await Promise.all(searchAddressesForTokenMatch); const validResults = results.filter((r) => r.result !== null); elizaLogger.info( - `Found ${validResults.length} valid results for ${addresses.length} addresses and ${symbols.length} symbols` + `Found ${validResults.length} valid results for ${addresses.length} addresses` ); - console.log(JSON.stringify(validResults, null, 2)); - // bail if no valid results if (validResults.length === 0) return null; - // for each result, get the chain from the search term - const resultsWithChains = validResults.map( - ({ searchTerm, address }) => ({ - searchTerm, - address, - chain: extractChain(address), - }) - ); - // STEP 3 - get metadata for all valid results and format them. This includes additional token information like social links, logo, etc. const resultsWithMetadata = await Promise.all( - resultsWithChains.map(({ address, chain }) => - getTokenMetadata(apiKey, address, chain) + validResults.map(({ address }) => + provider.fetchTokenMetadata(address, extractChain(address)) ) ); // STEP 4 - Format all results together - const completeResults = `The following data is available for the symbols and contract addresses requested: ${validResults + const completeResults = `The following data is available for the addresses requested: ${validResults .map( ({ searchTerm, result }, index) => - `Search term "${searchTerm}":\n${formatTokenInfo(result!, resultsWithMetadata[index])}` + `Address "${searchTerm}":\n${formatTokenInfo(result!, resultsWithMetadata[index])}` ) .join("\n\n")}`; - console.log(completeResults); - return completeResults; }, }; diff --git a/packages/plugin-birdeye/src/providers/agent-portfolio-provider.ts b/packages/plugin-birdeye/src/providers/agent-portfolio-provider.ts new file mode 100644 index 0000000000..cd2f2eb1b6 --- /dev/null +++ b/packages/plugin-birdeye/src/providers/agent-portfolio-provider.ts @@ -0,0 +1,46 @@ +import { IAgentRuntime, Memory, Provider, State } from "@elizaos/core"; +import { BirdeyeProvider } from "../birdeye"; +import { extractChain, formatPortfolio } from "../utils"; + +/** + * Agent portfolio data provider that queries Birdeye API for the agent's wallet address. + * When a wallet address is set, this provider fetches portfolio data to give the agent + * context about the agent's holdings when responding to queries. + * + * The provider: + * - Validates the agent's wallet address + * - Fetches current portfolio data from Birdeye including token balances and metadata + * - Makes this portfolio context available to the agent for responding to user queries + * about their holdings, token values, etc. + */ +export const agentPortfolioProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + _message: Memory, + _state?: State + ): Promise => { + try { + const provider = new BirdeyeProvider(runtime.cacheManager); + const walletAddr = runtime.getSetting("BIRDEYE_WALLET_ADDR"); + + if (!walletAddr) { + console.warn("No Birdeye wallet was specified"); + return ""; + } + + const chain = extractChain(walletAddr); + + const resp = await provider.fetchSearchWallets({ + wallet: walletAddr, + chain, + }); + + const portfolioText = formatPortfolio(resp?.data?.items || []); + + return `This is your wallet address: ${walletAddr}\n\nThis is your portfolio: [${portfolioText}]`; + } catch (error) { + console.error("Error fetching token data:", error); + return "Unable to fetch token information. Please try again later."; + } + }, +}; diff --git a/packages/plugin-birdeye/src/providers/symbol-search-provider.ts b/packages/plugin-birdeye/src/providers/symbol-search-provider.ts new file mode 100644 index 0000000000..11ca5b4f4e --- /dev/null +++ b/packages/plugin-birdeye/src/providers/symbol-search-provider.ts @@ -0,0 +1,87 @@ +import { + elizaLogger, + IAgentRuntime, + Memory, + Provider, + State, +} from "@elizaos/core"; +import { BirdeyeProvider } from "../birdeye"; +import { extractChain, extractSymbols, formatTokenInfo } from "../utils"; + +/** + * Searches message text for ALL token symbols and then enriches them with the basic token stats and metadata from Birdeye + */ +export const symbolSearchProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + const provider = new BirdeyeProvider(runtime.cacheManager); + + const messageText = message.content.text; + + // STEP 1 - Extract symbols + const symbols = extractSymbols(messageText); + + if (symbols.length === 0) return null; + + elizaLogger.info( + `Searching Birdeye provider for ${symbols.length} symbols` + ); + + // STEP 2 - Search Birdeye services for token matches based on symbols + const searchTokenResponses = symbols.map((symbol) => + provider.fetchSearchTokens({ + keyword: symbol, + limit: 1, + }) + ); + + const results = await Promise.all(searchTokenResponses); + const validResults = results.map( + (r, index) => + r.data.items.find( + (item) => + item.type === "token" && + item.result[0].symbol.toLowerCase() === + symbols[index].toLowerCase() + )?.result[0] + ); + + elizaLogger.info( + `Found ${validResults.length} valid results for ${symbols.length} symbols` + ); + + console.log(JSON.stringify(validResults, null, 2)); + + // bail if no valid results + if (validResults.length === 0) return null; + + // for each result, get the chain from the search term + const resultsWithChains = validResults.map((result) => ({ + symbol: result.symbol, + address: result.address, + chain: extractChain(result.address), + })); + + // STEP 3 - get metadata for all valid results and format them. This includes additional token information like social links, logo, etc. + const resultsWithMetadata = await Promise.all( + resultsWithChains.map(({ address, chain }) => + provider.fetchTokenMetadata(address, chain) + ) + ); + + // STEP 4 - Format all results together + const completeResults = `The following data is available for the symbols requested: ${validResults + .map( + (result, index) => + `Search term "${result.symbol}":\n${formatTokenInfo(result!, resultsWithMetadata[index])}` + ) + .join("\n\n")}`; + + console.log(completeResults); + + return completeResults; + }, +}; diff --git a/packages/plugin-birdeye/src/providers/wallet-portfolio-provider.ts b/packages/plugin-birdeye/src/providers/wallet-portfolio-provider.ts new file mode 100644 index 0000000000..94631c47bb --- /dev/null +++ b/packages/plugin-birdeye/src/providers/wallet-portfolio-provider.ts @@ -0,0 +1,75 @@ +import { + elizaLogger, + IAgentRuntime, + Memory, + Provider, + State, +} from "@elizaos/core"; +import { BirdeyeProvider } from "../birdeye"; +import { extractAddressesFromString } from "../utils"; + +/** + * Wallet portfolio provider that queries Birdeye API for a any potential wallet addresses the message. + * When a wallet address is set, this provider fetches portfolio data to give the agent + * context about the user's top holdings when responding to queries. + */ +export const walletPortfolioProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + try { + const provider = new BirdeyeProvider(runtime.cacheManager); + + const messageText = message.content.text; + + // STEP 1 - Extract addresses and symbols + const addresses = extractAddressesFromString(messageText); + + if (addresses.length === 0) return null; + + elizaLogger.info( + `Searching Birdeye provider for ${addresses.length} wallet addresses` + ); + + // STEP 2 - Search Birdeye services for wallet portfolio data + const searchAddressesForTokenMatch = addresses.map((address) => + provider.fetchSearchWallets({ + wallet: address.address, + chain: address.chain, + }) + ); + + // STEP 3 - Format the results together + const results = await Promise.all(searchAddressesForTokenMatch); + const validResults = results.filter((r) => r !== null); + + elizaLogger.info( + `Found ${validResults.length} valid results for ${addresses.length} addresses` + ); + + if (validResults.length === 0) return null; + + // Format portfolio data into readable text + const portfolioText = validResults + .map((wallet, index) => { + const tokens = wallet.data.items.slice(0, 5) || []; + const tokenList = tokens + .map( + (token) => + `${token.symbol}: $${token.valueUsd?.toLocaleString()}` + ) + .join(", "); + + return `Wallet ${addresses[index].address} holds: ${tokenList}`; + }) + .join("\n"); + + return portfolioText; + } catch (error) { + console.error("Error fetching wallet portfolio data:", error); + return "Unable to fetch wallet portfolio information. Please try again later."; + } + }, +}; diff --git a/packages/plugin-birdeye/src/services.ts b/packages/plugin-birdeye/src/services.ts deleted file mode 100644 index ef508b4d00..0000000000 --- a/packages/plugin-birdeye/src/services.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { elizaLogger } from "@elizaos/core"; -import { - SearchToken, - SearchTokenResponse, - SearchTokensOptions, -} from "./types/search-token"; -import { BirdeyeChain } from "./types/shared"; -import { TokenMetadataResponse } from "./types/token-metadata"; -import { - WalletDataItem, - WalletDataOptions, - WalletDataResponse, -} from "./types/wallet"; -import { BASE_URL, makeApiRequest } from "./utils"; - -export const searchTokens = async ( - apiKey: string, - options: SearchTokensOptions -): Promise => { - try { - const { keyword, chain = "all", limit = 1, offset = 0, type } = options; - - const params = new URLSearchParams({ - keyword, - limit: limit.toString(), - offset: offset.toString(), - chain: chain, - }); - - const url = `${BASE_URL}/defi/v3/search?${params.toString()}`; - - elizaLogger.info("Searching tokens from:", url); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - }, - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = (await response.json()) as SearchTokenResponse; - - // Extract tokens from the response - // if the search type is address, we only want to return the token that matches the address - const tokens = - type === "address" - ? data.data.items - .filter( - (item) => - item.type === "token" && - // only return the token that matches the address - item.result[0].address === keyword.toLowerCase() - ) - .flatMap((item) => item.result) - : data.data.items - .filter( - (item) => - item.type === "token" && - // only return the token that matches the symbol - item.result[0].symbol === keyword - ) - .flatMap((item) => item.result); - - elizaLogger.info("Found tokens:", tokens); - - return tokens; - } catch (error) { - elizaLogger.error("Error searching tokens:", error); - throw error; - } -}; - -export const searchWallets = async ( - apiKey: string, - options: WalletDataOptions -): Promise => { - try { - const { wallet, chain = "solana" } = options; - - const params = new URLSearchParams({ - wallet, - chain: chain, - }); - - const url = `${BASE_URL}/v1/wallet/token_list?${params.toString()}`; - - elizaLogger.info("Searching wallet data from:", url); - - const response = await fetch(url, { - headers: { - "X-API-KEY": apiKey, - "x-chain": chain, - }, - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = (await response.json()) as WalletDataResponse; - - elizaLogger.info("Birdeye response:", data); - - // Extract tokens from the response - // if the search type is address, we only want to return the token that matches the address - const walletData = data.data.items; - - elizaLogger.info("Found wallet data:", walletData); - - return walletData; - } catch (error) { - elizaLogger.error("Error searching tokens:", error); - throw error; - } -}; - -export const getTokenMetadata = async ( - apiKey: string, - address: string, - chain: BirdeyeChain -): Promise => { - try { - // Validate address format based on chain - const isValidAddress = (() => { - switch (chain) { - case "solana": - return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address); - case "sui": - return /^0x[a-fA-F0-9]{64}$/i.test(address); - case "ethereum": - case "arbitrum": - case "avalanche": - case "bsc": - case "optimism": - case "polygon": - case "base": - case "zksync": - return /^0x[a-fA-F0-9]{40}$/i.test(address); - default: - return false; - } - })(); - - if (!isValidAddress) { - elizaLogger.error( - `Invalid address format for ${chain}: ${address}` - ); - return null; - } - - const params = new URLSearchParams({ - address: address, - }); - const url = `${BASE_URL}/defi/v3/token/meta-data/single?${params.toString()}`; - - elizaLogger.info( - `Fetching token metadata for ${address} on ${chain} from:`, - url - ); - - return await makeApiRequest(url, { - apiKey, - chain, - }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching token metadata:", error.message); - } - return null; - } -}; diff --git a/packages/plugin-birdeye/src/tests/birdeye.test.ts b/packages/plugin-birdeye/src/tests/birdeye.test.ts index 7021926cb3..29a95d275a 100644 --- a/packages/plugin-birdeye/src/tests/birdeye.test.ts +++ b/packages/plugin-birdeye/src/tests/birdeye.test.ts @@ -1,6 +1,6 @@ import { ICacheManager } from "@elizaos/core"; import NodeCache from "node-cache"; -import { BirdeyeProvider } from "../providers/birdeye"; +import { BirdeyeProvider } from "../birdeye"; import { afterEach, beforeEach, describe, expect, it, Mock, vi } from "vitest"; diff --git a/packages/plugin-birdeye/src/types/search-token.ts b/packages/plugin-birdeye/src/types/search-token.ts index 10c150c1ec..347f5dc10f 100644 --- a/packages/plugin-birdeye/src/types/search-token.ts +++ b/packages/plugin-birdeye/src/types/search-token.ts @@ -1,4 +1,4 @@ -export interface SearchToken { +export interface SearchTokenItem { address: string; name: string; symbol: string; @@ -28,7 +28,7 @@ export interface SearchTokenResponse { data: { items: Array<{ type: string; - result: SearchToken[]; + result: SearchTokenItem[]; }>; }; success: boolean; diff --git a/packages/plugin-birdeye/src/types/token-list-v1.ts b/packages/plugin-birdeye/src/types/token-list-v1.ts new file mode 100644 index 0000000000..89d2cacc72 --- /dev/null +++ b/packages/plugin-birdeye/src/types/token-list-v1.ts @@ -0,0 +1,30 @@ +export interface TokenListV1Item { + address: string; + name: string; + symbol: string; + decimals: number; + liquidity: number; + mc: number; // market cap + v24hUSD: number; + v24hChangePercent: number; + lastTradeUnixTime: number; + logoURI: string; +} + +export interface TokenListV1Response { + success: boolean; + data: { + updateUnixTime: number; + updateTime: string; + tokens: TokenListV1Item[]; + total: number; + }; +} + +export interface TokenListV1Options { + sort_by?: string; + sort_type?: string; + limit?: number; + offset?: number; + min_liquidity?: number; +} diff --git a/packages/plugin-birdeye/src/types/token-metadata.ts b/packages/plugin-birdeye/src/types/token-metadata.ts index 935837896d..541ed20b39 100644 --- a/packages/plugin-birdeye/src/types/token-metadata.ts +++ b/packages/plugin-birdeye/src/types/token-metadata.ts @@ -1,18 +1,19 @@ -// Define explicit interface instead of using typeof export interface TokenMetadataResponse { - data: { - address: string; - symbol: string; - name: string; - decimals: number; - extensions: { - coingecko_id?: string; - website?: string; - twitter?: string; - discord?: string; - medium?: string; - }; - logo_uri?: string; - }; + data: TokenMetadataItem; success: boolean; } + +export interface TokenMetadataItem { + address: string; + symbol: string; + name: string; + decimals: number; + extensions: { + coingecko_id?: string; + website?: string; + twitter?: string; + discord?: string; + medium?: string; + }; + logo_uri?: string; +} diff --git a/packages/plugin-birdeye/src/types/wallet.ts b/packages/plugin-birdeye/src/types/wallet.ts index 2d97d5a763..f90d7c6b64 100644 --- a/packages/plugin-birdeye/src/types/wallet.ts +++ b/packages/plugin-birdeye/src/types/wallet.ts @@ -14,11 +14,11 @@ export interface WalletDataItem { uiAmount: number; chainId: string; logoURI: string; - priceUsd: number; - valueUsd: number; + priceUsd?: number; + valueUsd?: number; } export interface WalletDataOptions { wallet: string; - chain?: string; + chain: string; } diff --git a/packages/plugin-birdeye/src/utils.ts b/packages/plugin-birdeye/src/utils.ts index ec8d559171..b8fe634eb7 100644 --- a/packages/plugin-birdeye/src/utils.ts +++ b/packages/plugin-birdeye/src/utils.ts @@ -1,7 +1,8 @@ import { elizaLogger } from "@elizaos/core"; -import { SearchToken } from "./types/search-token"; +import { SearchTokenItem } from "./types/search-token"; import { BaseAddress, BirdeyeChain } from "./types/shared"; import { TokenMetadataResponse } from "./types/token-metadata"; +import { WalletDataItem } from "./types/wallet"; // Constants export const BASE_URL = "https://public-api.birdeye.so"; @@ -119,7 +120,7 @@ export const extractChain = (text: string): BirdeyeChain => { return "solana"; }; -export const extractContractAddresses = (text: string): BaseAddress[] => { +export const extractAddressesFromString = (text: string): BaseAddress[] => { const addresses: BaseAddress[] = []; // EVM-compatible chains (Ethereum, Arbitrum, Avalanche, BSC, Optimism, Polygon, Base, zkSync) @@ -128,7 +129,7 @@ export const extractContractAddresses = (text: string): BaseAddress[] => { addresses.push( ...evmAddresses.map((address) => ({ address, - chain: extractChain(address), + chain: "evm" as BirdeyeChain, // we don't yet know the chain but can assume it's EVM-compatible })) ); } @@ -365,7 +366,7 @@ export async function makeApiRequest( // Formatting helpers export const formatTokenInfo = ( - token: SearchToken, + token: SearchTokenItem, metadata?: TokenMetadataResponse ): string => { const priceFormatted = @@ -434,44 +435,41 @@ export const formatTokenInfo = ( }; // Extract symbols from text -export const extractSymbols = (text: string): string[] => { +export const extractSymbols = ( + text: string, + // loose mode will try to extract more symbols but may include false positives + // strict mode will only extract symbols that are clearly formatted as a symbol using $SOL format + mode: "strict" | "loose" = "strict" +): string[] => { const symbols = new Set(); - // Common words to exclude (avoid false positives) - const excludeWords = new Set([ - "USD", - "APY", - "API", - "NFT", - "DEX", - "CEX", - "APR", - "TVL", - ]); - - // Match patterns: - const patterns = [ - // $SYMBOL format - /\$([A-Z0-9]{2,10})\b/gi, - // After articles (a/an) - /\b(?:a|an)\s+([A-Z0-9]{2,10})\b/gi, - // Standalone caps - /\b[A-Z0-9]{2,10}\b/g, - // Quoted symbols - /["']([A-Z0-9]{2,10})["']/gi, - // Common price patterns - /\b([A-Z0-9]{2,10})\/USD\b/gi, - /\b([A-Z0-9]{2,10})-USD\b/gi, - ]; + // Match patterns - this may + const patterns = + mode === "strict" + ? [ + // $SYMBOL format + /\$([A-Z0-9]{2,10})\b/gi, + ] + : [ + // $SYMBOL format + /\$([A-Z0-9]{2,10})\b/gi, + // After articles (a/an) + /\b(?:a|an)\s+([A-Z0-9]{2,10})\b/gi, + // // Standalone caps + /\b[A-Z0-9]{2,10}\b/g, + // // Quoted symbols + /["']([A-Z0-9]{2,10})["']/gi, + // // Common price patterns + /\b([A-Z0-9]{2,10})\/USD\b/gi, + /\b([A-Z0-9]{2,10})-USD\b/gi, + ]; // Extract all matches patterns.forEach((pattern) => { const matches = text.matchAll(pattern); for (const match of matches) { const symbol = (match[1] || match[0]).toUpperCase(); - if (!excludeWords.has(symbol)) { - symbols.add(symbol); - } + symbols.add(symbol); } }); @@ -564,3 +562,21 @@ const formatSocialLinks = (data: TokenMetadataResponse["data"]): string => { return links.length > 0 ? links.join("\n") : "No social links available"; }; + +export const waitFor = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); + +export const formatPortfolio = (items: WalletDataItem[]) => { + if (!items?.length) return "No tokens found in portfolio"; + + return items + .map((item) => { + const value = item.valueUsd?.toFixed(2); + const amount = item.uiAmount?.toFixed(4); + return ( + `ā€¢ ${item.symbol || "Unknown Token"}: ${amount} tokens` + + `${value !== "0.00" ? ` (Value: $${value || "unknown"})` : ""}` + ); + }) + .join("\n"); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6562f8c455..062e71be5d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1005,7 +1005,7 @@ importers: version: link:../core '@solana/spl-token': specifier: 0.4.9 - version: 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) '@solana/web3.js': specifier: 1.95.8 version: 1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) @@ -1020,7 +1020,7 @@ importers: version: 6.0.0 fomo-sdk-solana: specifier: 1.3.2 - version: 1.3.2(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 1.3.2(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) form-data: specifier: 4.0.1 version: 4.0.1 @@ -1029,16 +1029,29 @@ importers: version: 5.1.2 pumpdotfun-sdk: specifier: 1.3.2 - version: 1.3.2(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(rollup@4.28.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 1.3.2(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(rollup@4.28.1)(typescript@5.7.2)(utf-8-validate@5.0.10) tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.1(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(typescript@5.6.3)(yaml@2.6.1) + version: 8.3.5(@swc/core@1.10.1(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(typescript@5.7.2)(yaml@2.6.1) vitest: specifier: 2.1.4 version: 2.1.4(@types/node@22.10.2)(jsdom@25.0.1(bufferutil@4.0.8)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 + devDependencies: + '@types/node': + specifier: ^22.10.2 + version: 22.10.2 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.10.2)(typescript@5.7.2) + tsconfig-paths: + specifier: ^4.2.0 + version: 4.2.0 + typescript: + specifier: ^5.7.2 + version: 5.7.2 packages/plugin-bootstrap: dependencies: @@ -17987,6 +18000,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + engines: {node: '>=14.17'} + hasBin: true + u3@0.1.1: resolution: {integrity: sha512-+J5D5ir763y+Am/QY6hXNRlwljIeRMZMGs0cT6qqZVVzzT3X3nFPXVyPOFRMOR4kupB0T8JnCdpWdp6Q/iXn3w==} @@ -19141,7 +19159,7 @@ snapshots: '@acuminous/bitsyntax@0.1.2': dependencies: buffer-more-ints: 1.0.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 safe-buffer: 5.1.2 transitivePeerDependencies: - supports-color @@ -21074,7 +21092,7 @@ snapshots: dependencies: '@scure/bip32': 1.6.0 abitype: 1.0.7(typescript@5.6.3)(zod@3.23.8) - axios: 1.7.9(debug@4.4.0) + axios: 1.7.9 axios-mock-adapter: 1.22.0(axios@1.7.9) axios-retry: 4.5.0(axios@1.7.9) bip32: 4.0.0 @@ -22780,7 +22798,7 @@ snapshots: '@eslint/config-array@0.19.1': dependencies: '@eslint/object-schema': 2.1.5 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -22806,7 +22824,7 @@ snapshots: '@eslint/eslintrc@3.2.0': dependencies: ajv: 6.12.6 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 @@ -25836,6 +25854,27 @@ snapshots: - typescript - utf-8-validate + '@raydium-io/raydium-sdk-v2@0.1.82-alpha(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10)': + dependencies: + '@solana/buffer-layout': 4.0.1 + '@solana/spl-token': 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + axios: 1.7.9 + big.js: 6.2.2 + bn.js: 5.2.1 + dayjs: 1.11.13 + decimal.js-light: 2.5.1 + lodash: 4.17.21 + toformat: 2.0.0 + tsconfig-paths: 4.2.0 + transitivePeerDependencies: + - bufferutil + - debug + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + '@react-icons/all-files@4.1.0(react@18.3.1)': dependencies: react: 18.3.1 @@ -26715,6 +26754,11 @@ snapshots: '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) typescript: 5.6.3 + '@solana/codecs-core@2.0.0-rc.1(typescript@5.7.2)': + dependencies: + '@solana/errors': 2.0.0-rc.1(typescript@5.7.2) + typescript: 5.7.2 + '@solana/codecs-data-structures@2.0.0-preview.2': dependencies: '@solana/codecs-core': 2.0.0-preview.2 @@ -26728,6 +26772,13 @@ snapshots: '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) typescript: 5.6.3 + '@solana/codecs-data-structures@2.0.0-rc.1(typescript@5.7.2)': + dependencies: + '@solana/codecs-core': 2.0.0-rc.1(typescript@5.7.2) + '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.7.2) + '@solana/errors': 2.0.0-rc.1(typescript@5.7.2) + typescript: 5.7.2 + '@solana/codecs-numbers@2.0.0-preview.2': dependencies: '@solana/codecs-core': 2.0.0-preview.2 @@ -26739,6 +26790,12 @@ snapshots: '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) typescript: 5.6.3 + '@solana/codecs-numbers@2.0.0-rc.1(typescript@5.7.2)': + dependencies: + '@solana/codecs-core': 2.0.0-rc.1(typescript@5.7.2) + '@solana/errors': 2.0.0-rc.1(typescript@5.7.2) + typescript: 5.7.2 + '@solana/codecs-strings@2.0.0-preview.2(fastestsmallesttextencoderdecoder@1.0.22)': dependencies: '@solana/codecs-core': 2.0.0-preview.2 @@ -26754,6 +26811,14 @@ snapshots: fastestsmallesttextencoderdecoder: 1.0.22 typescript: 5.6.3 + '@solana/codecs-strings@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': + dependencies: + '@solana/codecs-core': 2.0.0-rc.1(typescript@5.7.2) + '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.7.2) + '@solana/errors': 2.0.0-rc.1(typescript@5.7.2) + fastestsmallesttextencoderdecoder: 1.0.22 + typescript: 5.7.2 + '@solana/codecs@2.0.0-preview.2(fastestsmallesttextencoderdecoder@1.0.22)': dependencies: '@solana/codecs-core': 2.0.0-preview.2 @@ -26775,6 +26840,17 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder + '@solana/codecs@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': + dependencies: + '@solana/codecs-core': 2.0.0-rc.1(typescript@5.7.2) + '@solana/codecs-data-structures': 2.0.0-rc.1(typescript@5.7.2) + '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.7.2) + '@solana/codecs-strings': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/options': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + typescript: 5.7.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + '@solana/errors@2.0.0-preview.2': dependencies: chalk: 5.3.0 @@ -26786,6 +26862,12 @@ snapshots: commander: 12.1.0 typescript: 5.6.3 + '@solana/errors@2.0.0-rc.1(typescript@5.7.2)': + dependencies: + chalk: 5.3.0 + commander: 12.1.0 + typescript: 5.7.2 + '@solana/options@2.0.0-preview.2': dependencies: '@solana/codecs-core': 2.0.0-preview.2 @@ -26802,6 +26884,17 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder + '@solana/options@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': + dependencies: + '@solana/codecs-core': 2.0.0-rc.1(typescript@5.7.2) + '@solana/codecs-data-structures': 2.0.0-rc.1(typescript@5.7.2) + '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.7.2) + '@solana/codecs-strings': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/errors': 2.0.0-rc.1(typescript@5.7.2) + typescript: 5.7.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + '@solana/spl-token-group@0.0.4(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)': dependencies: '@solana/codecs': 2.0.0-preview.2(fastestsmallesttextencoderdecoder@1.0.22) @@ -26818,6 +26911,14 @@ snapshots: - fastestsmallesttextencoderdecoder - typescript + '@solana/spl-token-group@0.0.7(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': + dependencies: + '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/web3.js': 1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + - typescript + '@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': dependencies: '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) @@ -26826,6 +26927,14 @@ snapshots: - fastestsmallesttextencoderdecoder - typescript + '@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': + dependencies: + '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/web3.js': 1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + - typescript + '@solana/spl-token@0.4.6(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: '@solana/buffer-layout': 4.0.1 @@ -26841,6 +26950,21 @@ snapshots: - typescript - utf-8-validate + '@solana/spl-token@0.4.6(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10)': + dependencies: + '@solana/buffer-layout': 4.0.1 + '@solana/buffer-layout-utils': 0.2.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@solana/spl-token-group': 0.0.4(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22) + '@solana/spl-token-metadata': 0.1.6(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/web3.js': 1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + buffer: 6.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + '@solana/spl-token@0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: '@solana/buffer-layout': 4.0.1 @@ -26856,6 +26980,21 @@ snapshots: - typescript - utf-8-validate + '@solana/spl-token@0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10)': + dependencies: + '@solana/buffer-layout': 4.0.1 + '@solana/buffer-layout-utils': 0.2.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@solana/spl-token-group': 0.0.7(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/spl-token-metadata': 0.1.6(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/web3.js': 1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + buffer: 6.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + '@solana/spl-type-length-value@0.1.0': dependencies: buffer: 6.0.3 @@ -27887,7 +28026,7 @@ snapshots: '@typescript-eslint/types': 8.16.0 '@typescript-eslint/typescript-estree': 8.16.0(typescript@5.6.3) '@typescript-eslint/visitor-keys': 8.16.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 eslint: 9.16.0(jiti@2.4.2) optionalDependencies: typescript: 5.6.3 @@ -27920,7 +28059,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 8.16.0(typescript@5.6.3) '@typescript-eslint/utils': 8.16.0(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3) - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 eslint: 9.16.0(jiti@2.4.2) ts-api-utils: 1.4.3(typescript@5.6.3) optionalDependencies: @@ -27951,7 +28090,7 @@ snapshots: dependencies: '@typescript-eslint/types': 8.16.0 '@typescript-eslint/visitor-keys': 8.16.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 @@ -28701,7 +28840,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -29050,13 +29189,13 @@ snapshots: axios-mock-adapter@1.22.0(axios@1.7.9): dependencies: - axios: 1.7.9(debug@4.4.0) + axios: 1.7.9 fast-deep-equal: 3.1.3 is-buffer: 2.0.5 axios-retry@4.5.0(axios@1.7.9): dependencies: - axios: 1.7.9(debug@4.4.0) + axios: 1.7.9 is-retry-allowed: 2.2.0 axios@0.21.4: @@ -29067,7 +29206,7 @@ snapshots: axios@0.27.2: dependencies: - follow-redirects: 1.15.9(debug@4.4.0) + follow-redirects: 1.15.9 form-data: 4.0.1 transitivePeerDependencies: - debug @@ -29096,6 +29235,14 @@ snapshots: transitivePeerDependencies: - debug + axios@1.7.9: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.1 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + axios@1.7.9(debug@4.4.0): dependencies: follow-redirects: 1.15.9(debug@4.4.0) @@ -31131,6 +31278,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.0: + dependencies: + ms: 2.1.3 + debug@4.4.0(supports-color@5.5.0): dependencies: ms: 2.1.3 @@ -31993,7 +32144,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 escape-string-regexp: 4.0.0 eslint-scope: 8.2.0 eslint-visitor-keys: 4.2.0 @@ -32576,6 +32727,8 @@ snapshots: async: 0.2.10 which: 1.3.1 + follow-redirects@1.15.9: {} + follow-redirects@1.15.9(debug@4.3.7): optionalDependencies: debug: 4.3.7 @@ -32600,6 +32753,22 @@ snapshots: - typescript - utf-8-validate + fomo-sdk-solana@1.3.2(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10): + dependencies: + '@coral-xyz/anchor': 0.30.1(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@raydium-io/raydium-sdk-v2': 0.1.82-alpha(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@solana/spl-token': 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + bs58: 6.0.0 + coral-xyz3: '@coral-xyz/anchor@0.29.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)' + transitivePeerDependencies: + - bufferutil + - debug + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + for-each@0.3.3: dependencies: is-callable: 1.2.7 @@ -33608,7 +33777,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -33666,14 +33835,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -36991,7 +37160,7 @@ snapshots: '@yarnpkg/lockfile': 1.1.0 '@yarnpkg/parsers': 3.0.0-rc.46 '@zkochan/js-yaml': 0.0.7 - axios: 1.7.9(debug@4.4.0) + axios: 1.7.9 chalk: 4.1.2 cli-cursor: 3.1.0 cli-spinners: 2.6.1 @@ -38680,6 +38849,20 @@ snapshots: - typescript - utf-8-validate + pumpdotfun-sdk@1.3.2(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(rollup@4.28.1)(typescript@5.7.2)(utf-8-validate@5.0.10): + dependencies: + '@coral-xyz/anchor': 0.30.1(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@rollup/plugin-json': 6.1.0(rollup@4.28.1) + '@solana/spl-token': 0.4.6(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - encoding + - fastestsmallesttextencoderdecoder + - rollup + - typescript + - utf-8-validate + punycode.js@2.3.1: {} punycode@1.4.1: {} @@ -40000,7 +40183,7 @@ snapshots: socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -40904,6 +41087,26 @@ snapshots: optionalDependencies: '@swc/core': 1.10.1(@swc/helpers@0.5.15) + ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.10.2)(typescript@5.7.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 22.10.2 + acorn: 8.14.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.7.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optionalDependencies: + '@swc/core': 1.10.1(@swc/helpers@0.5.15) + ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -40972,12 +41175,40 @@ snapshots: - tsx - yaml + tsup@8.3.5(@swc/core@1.10.1(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(typescript@5.7.2)(yaml@2.6.1): + dependencies: + bundle-require: 5.0.0(esbuild@0.24.0) + cac: 6.7.14 + chokidar: 4.0.2 + consola: 3.2.3 + debug: 4.4.0 + esbuild: 0.24.0 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(jiti@2.4.2)(postcss@8.4.49)(yaml@2.6.1) + resolve-from: 5.0.0 + rollup: 4.28.1 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tinyexec: 0.3.1 + tinyglobby: 0.2.10 + tree-kill: 1.2.2 + optionalDependencies: + '@swc/core': 1.10.1(@swc/helpers@0.5.15) + postcss: 8.4.49 + typescript: 5.7.2 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + tty-browserify@0.0.1: {} tuf-js@2.2.1: dependencies: '@tufjs/models': 2.0.1 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 make-fetch-happen: 13.0.1 transitivePeerDependencies: - supports-color @@ -41139,6 +41370,8 @@ snapshots: typescript@5.6.3: {} + typescript@5.7.2: {} + u3@0.1.1: {} uc.micro@2.1.0: {} @@ -41617,7 +41850,7 @@ snapshots: vite-node@2.1.4(@types/node@22.10.2)(terser@5.37.0): dependencies: cac: 6.7.14 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 pathe: 1.1.2 vite: 5.4.11(@types/node@22.10.2)(terser@5.37.0) transitivePeerDependencies: @@ -41634,7 +41867,7 @@ snapshots: vite-node@2.1.5(@types/node@22.10.2)(terser@5.37.0): dependencies: cac: 6.7.14 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 es-module-lexer: 1.5.4 pathe: 1.1.2 vite: 5.4.11(@types/node@22.10.2)(terser@5.37.0) @@ -41711,7 +41944,7 @@ snapshots: '@vitest/spy': 2.1.4 '@vitest/utils': 2.1.4 chai: 5.1.2 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 expect-type: 1.1.0 magic-string: 0.30.17 pathe: 1.1.2 @@ -41747,7 +41980,7 @@ snapshots: '@vitest/spy': 2.1.5 '@vitest/utils': 2.1.5 chai: 5.1.2 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0 expect-type: 1.1.0 magic-string: 0.30.17 pathe: 1.1.2 From 4cd6ebb6113650f44452696eb166d7e3035320b9 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Tue, 31 Dec 2024 11:52:27 -0800 Subject: [PATCH 07/33] chore: got basic tests working --- .vscode/launch.json | 31 - .../src/actions/defi/get-price-history.ts | 434 ----------- .../src/actions/defi/get-token-metadata.ts | 185 ----- .../src/actions/defi/networks.ts | 108 --- .../src/actions/defi/networks.utils.ts | 27 - .../src/actions/searchTokens.ts | 226 ++++++ .../src/actions/searchWallets.ts | 169 ++++ .../src/actions/test-all-endpoints.ts | 331 ++++++++ packages/plugin-birdeye/src/birdeye.ts | 730 ++++++++++++++---- packages/plugin-birdeye/src/constants.ts | 74 +- packages/plugin-birdeye/src/index.ts | 24 +- .../src/providers/address-search-provider.ts | 79 -- .../src/providers/agent-portfolio-provider.ts | 6 +- .../src/providers/symbol-search-provider.ts | 87 --- .../providers/wallet-portfolio-provider.ts | 75 -- .../plugin-birdeye/src/tests/birdeye.test.ts | 648 ++++++++++------ .../plugin-birdeye/src/types/api/common.ts | 293 +++++++ packages/plugin-birdeye/src/types/api/defi.ts | 218 ++++++ packages/plugin-birdeye/src/types/api/pair.ts | 198 +++++ .../plugin-birdeye/src/types/api/search.ts | 84 ++ .../plugin-birdeye/src/types/api/token.ts | 634 +++++++++++++++ .../plugin-birdeye/src/types/api/trader.ts | 75 ++ .../plugin-birdeye/src/types/api/wallet.ts | 180 +++++ .../plugin-birdeye/src/types/search-token.ts | 43 -- packages/plugin-birdeye/src/types/shared.ts | 6 +- .../plugin-birdeye/src/types/token-list-v1.ts | 30 - .../src/types/token-metadata.ts | 19 - packages/plugin-birdeye/src/types/wallet.ts | 24 - packages/plugin-birdeye/src/utils.ts | 63 +- 29 files changed, 3556 insertions(+), 1545 deletions(-) delete mode 100644 packages/plugin-birdeye/src/actions/defi/get-price-history.ts delete mode 100644 packages/plugin-birdeye/src/actions/defi/get-token-metadata.ts delete mode 100644 packages/plugin-birdeye/src/actions/defi/networks.ts delete mode 100644 packages/plugin-birdeye/src/actions/defi/networks.utils.ts create mode 100644 packages/plugin-birdeye/src/actions/searchTokens.ts create mode 100644 packages/plugin-birdeye/src/actions/searchWallets.ts create mode 100644 packages/plugin-birdeye/src/actions/test-all-endpoints.ts delete mode 100644 packages/plugin-birdeye/src/providers/address-search-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/symbol-search-provider.ts delete mode 100644 packages/plugin-birdeye/src/providers/wallet-portfolio-provider.ts create mode 100644 packages/plugin-birdeye/src/types/api/common.ts create mode 100644 packages/plugin-birdeye/src/types/api/defi.ts create mode 100644 packages/plugin-birdeye/src/types/api/pair.ts create mode 100644 packages/plugin-birdeye/src/types/api/search.ts create mode 100644 packages/plugin-birdeye/src/types/api/token.ts create mode 100644 packages/plugin-birdeye/src/types/api/trader.ts create mode 100644 packages/plugin-birdeye/src/types/api/wallet.ts delete mode 100644 packages/plugin-birdeye/src/types/search-token.ts delete mode 100644 packages/plugin-birdeye/src/types/token-list-v1.ts delete mode 100644 packages/plugin-birdeye/src/types/token-metadata.ts delete mode 100644 packages/plugin-birdeye/src/types/wallet.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 57b7269fe5..0fbdde8983 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -18,36 +18,5 @@ "/**" ] }, - { - "name": "Current TS File", - "type": "node", - "request": "launch", - "args": [ - "${relativeFile}", - "-p", - "${workspaceFolder}/tsconfig.json" - ], - "runtimeVersion": "23.3.0", - "runtimeArgs": [ - "-r", - "ts-node/register", - "-r", - "tsconfig-paths/register", - "--loader", - "ts-node/esm", - "--nolazy" - ], - "cwd": "${workspaceRoot}", - "internalConsoleOptions": "openOnSessionStart", - "envFile": "${workspaceFolder}/.env", - "smartStep": true, - "skipFiles": [ - "/**", - "node_modules/**" - ], - "env": { - "TS_NODE_COMPILER_OPTIONS": "{\"module\": \"commonjs\"}" - } - } ] } \ No newline at end of file diff --git a/packages/plugin-birdeye/src/actions/defi/get-price-history.ts b/packages/plugin-birdeye/src/actions/defi/get-price-history.ts deleted file mode 100644 index 3e501a6904..0000000000 --- a/packages/plugin-birdeye/src/actions/defi/get-price-history.ts +++ /dev/null @@ -1,434 +0,0 @@ -import { - Action, - ActionExample, - Content, - elizaLogger, - Handler, - HandlerCallback, - IAgentRuntime, - Memory, - State, -} from "@elizaos/core"; -import { getTokenMetadata } from "../../services"; -import { BirdeyeChain } from "../../types/shared"; -import { TokenMetadataResponse } from "../../types/token-metadata"; -import { - BASE_URL, - extractAddressesFromString, - extractChain, - extractTimeRange, - formatTimestamp, - formatValue, - makeApiRequest, -} from "../../utils"; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const exampleResponse = { - success: true, - data: { - items: [ - { - unixTime: 1726670700, - value: 127.97284640184616, - }, - { - unixTime: 1726671600, - value: 128.04188346328968, - }, - { - unixTime: 1726672500, - value: 127.40223856228901, - }, - ], - }, -}; - -type PriceHistoryResponse = typeof exampleResponse; - -type TimeInterval = - | "1m" - | "3m" - | "5m" - | "15m" - | "30m" - | "1H" - | "2H" - | "4H" - | "6H" - | "8H" - | "12H" - | "1D" - | "3D" - | "1W" - | "1M"; - -const TIME_INTERVALS: Record = { - "1m": "1 minute", - "3m": "3 minutes", - "5m": "5 minutes", - "15m": "15 minutes", - "30m": "30 minutes", - "1H": "1 hour", - "2H": "2 hours", - "4H": "4 hours", - "6H": "6 hours", - "8H": "8 hours", - "12H": "12 hours", - "1D": "1 day", - "3D": "3 days", - "1W": "1 week", - "1M": "1 month", -}; - -const DEFAULT_INTERVAL: TimeInterval = "1D"; - -const extractTimeInterval = (text: string): TimeInterval => { - // First try to match exact interval codes - const intervalMatch = text.match( - /\b(1m|3m|5m|15m|30m|1H|2H|4H|6H|8H|12H|1D|3D|1W|1M)\b/i - ); - if (intervalMatch) { - return intervalMatch[1].toUpperCase() as TimeInterval; - } - - // Then try to match written intervals - const lowerText = text.toLowerCase(); - for (const [interval, description] of Object.entries(TIME_INTERVALS)) { - if (lowerText.includes(description.toLowerCase())) { - return interval as TimeInterval; - } - } - - // Common variations - if (lowerText.includes("hourly")) return "1H"; - if (lowerText.includes("daily")) return "1D"; - if (lowerText.includes("weekly")) return "1W"; - if (lowerText.includes("monthly")) return "1M"; - - return DEFAULT_INTERVAL; -}; - -// Constants for keyword matching -const PRICE_HISTORY_KEYWORDS = [ - "price history", - "historical price", - "price chart", - "price trend", - "price movement", - "price changes", - "price over time", - "price timeline", - "price performance", - "price data", - "historical data", - "price analysis", - "price tracking", - "price evolution", -] as const; - -// Helper function to check if text contains price history related keywords -const containsPriceHistoryKeyword = (text: string): boolean => { - return PRICE_HISTORY_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const getPriceHistory = async ( - apiKey: string, - contractAddress: string, - startTime: number, - endTime: number, - chain: BirdeyeChain, - interval: TimeInterval = DEFAULT_INTERVAL -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - time_from: startTime.toString(), - time_to: endTime.toString(), - interval: interval.toLowerCase(), - }); - const url = `${BASE_URL}/defi/price_history_unix?${params.toString()}`; - - elizaLogger.info( - `Fetching price history for token ${contractAddress} from ${new Date( - startTime * 1000 - ).toLocaleString()} to ${new Date( - endTime * 1000 - ).toLocaleString()} on ${chain} with ${TIME_INTERVALS[interval]} interval from:`, - url - ); - - return await makeApiRequest(url, { - apiKey, - chain, - }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching price history:", error.message); - } - return null; - } -}; - -const formatPriceHistoryResponse = ( - data: PriceHistoryResponse, - tokenMetadata: TokenMetadataResponse | null, - timeRange: { start: number; end: number }, - chain: BirdeyeChain, - interval: TimeInterval -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - const startDate = formatTimestamp(timeRange.start); - const endDate = formatTimestamp(timeRange.end); - - let tokenInfo = "Unknown Token"; - let tokenLinks = ""; - - if (tokenMetadata?.success) { - const { name, symbol, extensions } = tokenMetadata.data; - tokenInfo = `${name} (${symbol})`; - - const links: string[] = []; - if (extensions.website) links.push(`[Website](${extensions.website})`); - if (extensions.coingecko_id) - links.push( - `[CoinGecko](https://www.coingecko.com/en/coins/${extensions.coingecko_id})` - ); - if (links.length > 0) { - tokenLinks = `\nšŸ“Œ More Information: ${links.join(" ā€¢ ")}`; - } - } - - let response = `Price History for ${tokenInfo} on ${chainName}${tokenLinks}\n`; - response += `Period: ${startDate} to ${endDate} (${TIME_INTERVALS[interval]} intervals)\n\n`; - - if (!data.success || !data.data.items || data.data.items.length === 0) { - return response + "No price data found for this period."; - } - - // Calculate summary statistics - const prices = data.data.items.map((d) => d.value); - const startPrice = data.data.items[0].value; - const endPrice = data.data.items[data.data.items.length - 1].value; - const priceChange = ((endPrice - startPrice) / startPrice) * 100; - const highestPrice = Math.max(...prices); - const lowestPrice = Math.min(...prices); - const averagePrice = prices.reduce((a, b) => a + b, 0) / prices.length; - const volatility = ((highestPrice - lowestPrice) / averagePrice) * 100; - - response += `šŸ“Š Summary\n`; - response += `ā€¢ Start Price: ${formatValue(startPrice)}\n`; - response += `ā€¢ End Price: ${formatValue(endPrice)}\n`; - response += `ā€¢ Price Change: ${priceChange >= 0 ? "+" : ""}${priceChange.toFixed(2)}%\n`; - response += `ā€¢ Highest Price: ${formatValue(highestPrice)}\n`; - response += `ā€¢ Lowest Price: ${formatValue(lowestPrice)}\n`; - response += `ā€¢ Average Price: ${formatValue(averagePrice)}\n`; - response += `ā€¢ Volatility: ${volatility.toFixed(2)}%\n\n`; - - // Add trend analysis - const trendStrength = Math.abs(priceChange); - let trendAnalysis = ""; - if (trendStrength < 1) { - trendAnalysis = "Price has remained relatively stable"; - } else if (trendStrength < 5) { - trendAnalysis = - priceChange > 0 - ? "Price shows slight upward movement" - : "Price shows slight downward movement"; - } else if (trendStrength < 10) { - trendAnalysis = - priceChange > 0 - ? "Price demonstrates moderate upward trend" - : "Price demonstrates moderate downward trend"; - } else { - trendAnalysis = - priceChange > 0 - ? "Price exhibits strong upward momentum" - : "Price exhibits strong downward momentum"; - } - - response += `šŸ“ˆ Trend Analysis\n`; - response += `ā€¢ ${trendAnalysis}\n`; - response += `ā€¢ Volatility is ${volatility < 10 ? "low" : volatility < 25 ? "moderate" : "high"}\n\n`; - - // Show key price points - response += `šŸ”‘ Key Price Points\n`; - const keyPoints = [ - { - label: "Start", - price: data.data.items[0].value, - timestamp: data.data.items[0].unixTime, - }, - { - label: "High", - price: highestPrice, - timestamp: data.data.items[prices.indexOf(highestPrice)].unixTime, - }, - { - label: "Low", - price: lowestPrice, - timestamp: data.data.items[prices.indexOf(lowestPrice)].unixTime, - }, - { - label: "End", - price: data.data.items[data.data.items.length - 1].value, - timestamp: data.data.items[data.data.items.length - 1].unixTime, - }, - ]; - - keyPoints.forEach((point) => { - response += `ā€¢ ${point.label}: ${formatValue(point.price)} (${formatTimestamp(point.timestamp)})\n`; - }); - - return response; -}; - -export const getPriceHistoryAction: Action = { - name: "GET_PRICE_HISTORY", - similes: [ - "SHOW_PRICE_HISTORY", - "VIEW_PRICE_HISTORY", - "CHECK_PRICE_HISTORY", - "DISPLAY_PRICE_HISTORY", - "ANALYZE_PRICE_HISTORY", - "GET_HISTORICAL_PRICES", - "SHOW_HISTORICAL_PRICES", - "VIEW_PRICE_TREND", - "CHECK_PRICE_TREND", - "ANALYZE_PRICE_TREND", - "PRICE_PERFORMANCE", - "TOKEN_PERFORMANCE", - ], - description: - "Retrieve and analyze historical price data for a token, including price changes, trends, and key statistics over a specified time period.", - validate: async ( - _runtime: IAgentRuntime, - message: Memory, - _state: State | undefined - ): Promise => { - return containsPriceHistoryKeyword(message.content.text); - }, - handler: (async ( - runtime: IAgentRuntime, - message: Memory, - _state: State | undefined, - _options: any, - callback: HandlerCallback - ): Promise => { - const callbackData: Content = { - text: "", - action: "GET_PRICE_HISTORY_RESPONSE", - source: message.content.source, - }; - - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - callbackData.text = - "I'm unable to fetch the price history due to missing API credentials."; - await callback(callbackData); - return callbackData; - } - - const messageText = message.content.text; - const addresses = extractAddressesFromString(messageText); - if (addresses.length === 0) { - callbackData.text = - "I couldn't find a valid token address in your message."; - await callback(callbackData); - return callbackData; - } - - const chain = extractChain(messageText); - const timeRange = extractTimeRange(messageText); - const interval = extractTimeInterval(messageText); - - // First fetch token metadata - const tokenMetadata = await getTokenMetadata( - apiKey, - addresses[0].toString(), - chain as BirdeyeChain - ); - - elizaLogger.info( - `PRICE HISTORY action activated for token ${addresses[0]} from ${new Date( - timeRange.start * 1000 - ).toLocaleString()} to ${new Date( - timeRange.end * 1000 - ).toLocaleString()} on ${chain} with ${TIME_INTERVALS[interval]} interval` - ); - - const priceData = await getPriceHistory( - apiKey, - addresses[0].toString(), - timeRange.start, - timeRange.end, - chain as BirdeyeChain, - interval - ); - - if (!priceData) { - callbackData.text = - "I apologize, but I couldn't retrieve the price history data at the moment."; - await callback(callbackData); - return callbackData; - } - - callbackData.text = formatPriceHistoryResponse( - priceData, - tokenMetadata, - timeRange, - chain as BirdeyeChain, - interval - ); - await callback(callbackData); - return callbackData; - }) as Handler, - examples: [ - [ - { - user: "{{user1}}", - content: { - text: "Show me the daily price history for token 0x1234... on Ethereum for the last week", - }, - }, - { - user: "{{user2}}", - content: { - text: "Here's the detailed daily price history analysis for the token, including price changes, trends, and key statistics over the specified period.", - action: "GET_PRICE_HISTORY", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { - text: "What's the hourly price trend for ABC123... on Solana?", - }, - }, - { - user: "{{user2}}", - content: { - text: "I'll analyze the hourly price history and provide you with a comprehensive overview of the token's performance.", - action: "GET_PRICE_HISTORY", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { - text: "Get 5-minute interval price data for token XYZ... on BSC", - }, - }, - { - user: "{{user2}}", - content: { - text: "I'll fetch the price history with 5-minute intervals and analyze the detailed price movements.", - action: "GET_PRICE_HISTORY", - }, - }, - ], - ] as ActionExample[][], -}; diff --git a/packages/plugin-birdeye/src/actions/defi/get-token-metadata.ts b/packages/plugin-birdeye/src/actions/defi/get-token-metadata.ts deleted file mode 100644 index bc76b9358d..0000000000 --- a/packages/plugin-birdeye/src/actions/defi/get-token-metadata.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { - Action, - ActionExample, - Content, - elizaLogger, - Handler, - HandlerCallback, - IAgentRuntime, - Memory, - State, -} from "@elizaos/core"; -import { BirdeyeChain } from "../../types/shared"; -import { - CHAIN_ALIASES, - CHAIN_KEYWORDS, - extractAddressesFromString, - extractChain, -} from "../../utils"; - -// Constants for keyword matching -const METADATA_KEYWORDS = [ - "metadata", - "token info", - "token information", - "token details", - "token data", - "token description", - "token profile", - "token overview", - "token stats", - "token statistics", - "token social", - "token links", - "token website", - "token socials", -] as const; - -// Helper function to check if text contains metadata-related keywords -const containsMetadataKeyword = (text: string): boolean => { - return METADATA_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -export const getTokenMetadataAction: Action = { - name: "GET_TOKEN_METADATA", - similes: [ - "SHOW_TOKEN_INFO", - "VIEW_TOKEN_DETAILS", - "CHECK_TOKEN_METADATA", - "DISPLAY_TOKEN_INFO", - "GET_TOKEN_DETAILS", - "TOKEN_INFORMATION", - "TOKEN_PROFILE", - "TOKEN_OVERVIEW", - "TOKEN_SOCIAL_LINKS", - "TOKEN_STATISTICS", - "TOKEN_DESCRIPTION", - ], - description: - "Retrieve and display comprehensive token metadata including basic information, description, social links, and other relevant details.", - validate: async ( - _runtime: IAgentRuntime, - message: Memory, - _state: State | undefined - ): Promise => { - return containsMetadataKeyword(message.content.text); - }, - handler: (async ( - runtime: IAgentRuntime, - message: Memory, - _state: State | undefined, - _options: any, - callback: HandlerCallback - ): Promise => { - const callbackData: Content = { - text: "", - action: "GET_TOKEN_METADATA_RESPONSE", - source: message.content.source, - }; - - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - callbackData.text = - "I'm unable to fetch the token metadata due to missing API credentials."; - await callback(callbackData); - return callbackData; - } - - const messageText = message.content.text; - const addresses = extractAddressesFromString(messageText); - const chain = extractChain(messageText); - - // Check if a specific chain was mentioned (including aliases) - const normalizedText = messageText.toLowerCase(); - const isChainMentioned = - CHAIN_KEYWORDS.some((keyword) => - normalizedText.includes(keyword.toLowerCase()) - ) || - Object.keys(CHAIN_ALIASES).some((alias) => - normalizedText.includes(alias.toLowerCase()) - ); - - if (addresses.length === 0) { - callbackData.text = isChainMentioned - ? `I couldn't find a valid token address for ${chain} chain in your message. ${chain} addresses should match the format: ${getChainAddressFormat( - chain as BirdeyeChain - )}` - : "I couldn't find a valid token address in your message."; - await callback(callbackData); - return callbackData; - } - - await callback(callbackData); - return callbackData; - }) as Handler, - examples: [ - [ - { - user: "{{user1}}", - content: { - text: "Show me the token information for 0x1234... on Ethereum", - }, - }, - { - user: "{{user2}}", - content: { - text: "Here's the detailed token metadata including basic information, social links, and other relevant details.", - action: "GET_TOKEN_METADATA", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { - text: "What are the token details for ABC123... on Solana?", - }, - }, - { - user: "{{user2}}", - content: { - text: "I'll fetch and display the comprehensive token profile with all available information.", - action: "GET_TOKEN_METADATA", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { - text: "Get me the social links and description for token XYZ... on BSC", - }, - }, - { - user: "{{user2}}", - content: { - text: "I'll retrieve the token's metadata including its social media links and description.", - action: "GET_TOKEN_METADATA", - }, - }, - ], - ] as ActionExample[][], -}; - -const getChainAddressFormat = (chain: BirdeyeChain): string => { - switch (chain) { - case "solana": - return "Base58 string (32-44 characters)"; - case "sui": - return "0x followed by 64 hexadecimal characters, optionally followed by ::module::type"; - case "ethereum": - case "arbitrum": - case "avalanche": - case "bsc": - case "optimism": - case "polygon": - case "base": - case "zksync": - return "0x followed by 40 hexadecimal characters"; - default: - return "unknown format"; - } -}; diff --git a/packages/plugin-birdeye/src/actions/defi/networks.ts b/packages/plugin-birdeye/src/actions/defi/networks.ts deleted file mode 100644 index 415d9f2080..0000000000 --- a/packages/plugin-birdeye/src/actions/defi/networks.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { - Action, - ActionExample, - Content, - elizaLogger, - Handler, - HandlerCallback, - IAgentRuntime, - Memory, - State, -} from "@elizaos/core"; -import { BASE_URL, makeApiRequest } from "../../utils.ts"; -import { containsNetworkKeyword, NetworksResponse } from "./networks.utils.ts"; - -export const getSupportedNetworksAction: Action = { - name: "GET_SUPPORTED_NETWORKS", - similes: [ - "LIST_NETWORKS", - "SHOW_NETWORKS", - "AVAILABLE_NETWORKS", - "SUPPORTED_CHAINS", - "LIST_CHAINS", - "SHOW_CHAINS", - "NETWORK_SUPPORT", - "CHAIN_SUPPORT", - ], - description: - "Retrieve and display the list of networks supported by the Birdeye API for token information, swaps, prices, and market data.", - validate: async ( - _runtime: IAgentRuntime, - message: Memory, - _state: State | undefined - ): Promise => { - return containsNetworkKeyword(message.content.text); - }, - handler: (async ( - runtime: IAgentRuntime, - message: Memory, - _state: State | undefined, - _options: any, - callback: HandlerCallback - ): Promise => { - const callbackData: Content = { - text: "", - action: "GET_SUPPORTED_NETWORKS_RESPONSE", - source: message.content.source, - }; - - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - callbackData.text = - "I'm unable to fetch the supported networks due to missing API credentials."; - await callback(callbackData); - return callbackData; - } - - elizaLogger.info("Fetching supported networks"); - const url = `${BASE_URL}/defi/networks`; - - const networksData = await makeApiRequest(url, { - apiKey, - }); - - if (!networksData) { - callbackData.text = - "I apologize, but I couldn't retrieve the list of supported networks at the moment."; - await callback(callbackData); - return callbackData; - } - - callbackData.text = `Currently supported networks are: ${networksData.data.join(", ")}`; - await callback(callbackData); - return callbackData; - }) as Handler, - examples: [ - [ - { - user: "{{user1}}", - content: { - text: "What networks are supported?", - }, - }, - { - user: "{{user2}}", - content: { - text: "Here are the currently supported networks: solana, ethereum, arbitrum, avalanche, bsc, optimism, polygon, base, zksync, sui", - action: "GET_SUPPORTED_NETWORKS", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { - text: "Show me the available chains", - }, - }, - { - user: "{{user2}}", - content: { - text: "The available chains are: solana, ethereum, arbitrum, avalanche, bsc, optimism, polygon, base, zksync, sui", - action: "GET_SUPPORTED_NETWORKS", - }, - }, - ], - ] as ActionExample[][], -}; diff --git a/packages/plugin-birdeye/src/actions/defi/networks.utils.ts b/packages/plugin-birdeye/src/actions/defi/networks.utils.ts deleted file mode 100644 index eefe89597c..0000000000 --- a/packages/plugin-birdeye/src/actions/defi/networks.utils.ts +++ /dev/null @@ -1,27 +0,0 @@ -export interface NetworksResponse { - success: boolean; - data: string[]; -} - -// Constants for keyword matching -export const NETWORK_KEYWORDS = [ - "supported networks", - "available networks", - "supported chains", - "available chains", - "which networks", - "which chains", - "list networks", - "list chains", - "show networks", - "show chains", - "network support", - "chain support", -] as const; - -// Helper function to check if text contains network-related keywords -export const containsNetworkKeyword = (text: string): boolean => { - return NETWORK_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; diff --git a/packages/plugin-birdeye/src/actions/searchTokens.ts b/packages/plugin-birdeye/src/actions/searchTokens.ts new file mode 100644 index 0000000000..dfcb0de4eb --- /dev/null +++ b/packages/plugin-birdeye/src/actions/searchTokens.ts @@ -0,0 +1,226 @@ +import { + Action, + ActionExample, + composeContext, + elizaLogger, + generateText, + ModelClass, + type IAgentRuntime, + type Memory, + type State, +} from "@elizaos/core"; +import { BirdeyeProvider } from "../birdeye"; +import { extractChain, extractSymbols } from "../utils"; + +const extractTokenSymbolsTemplate = `Given the recent message below: +{{recentMessages}} +Extract all token symbols mentioned in the message. Look for: +- Symbols prefixed with $ (e.g. $SOL, $ETH) +- Well-known symbols in any case (e.g. BTC, eth, SOL) +- Other symbols that are in all caps (e.g. SOL, SUI, NUER) +- Quoted symbols + +When symbols are in lowercase (btc, eth, sol), convert to well-known format (BTC, ETH, SOL). +For mixed case symbols (SOl, eTH), include them as-is unless quoted. + +Respond with a JSON array containing only the extracted symbols, no extra description needed. +Example: +Message: "Check $SOL and btc prices" +Response: ["SOL", "BTC"]`; + +const extractTokenSymbolsFromMessage = async ( + runtime: IAgentRuntime, + message: Memory, + state: State +) => { + const context = composeContext({ + state, + template: extractTokenSymbolsTemplate, + }); + + const response = await generateText({ + runtime, + context, + modelClass: ModelClass.LARGE, + }); + + try { + const regex = new RegExp(/\[(.+)\]/gms); + const normalized = response && regex.exec(response)?.[0]; + return normalized ? JSON.parse(normalized) : []; + } catch { + return []; + } +}; + +const formatTokenReport = (token, metadata, security, volume) => { + let output = `*Token Security and Trade Report*\n`; + output += `Token symbol: ${token.symbol}\n`; + output += `Token Address: ${token.address}\n\n`; + + if (security?.data) { + output += `*Ownership Distribution:*\n`; + output += `- Owner Balance: ${security.data.ownerBalance}\n`; + output += `- Creator Balance: ${security.data.creatorBalance}\n`; + output += `- Owner Percentage: ${security.data.ownerPercentage}%\n`; + output += `- Creator Percentage: ${security.data.creatorPercentage}%\n`; + output += `- Top 10 Holders Balance: ${security.data.top10HolderBalance}\n`; + output += `- Top 10 Holders Percentage: ${security.data.top10HolderPercent}%\n\n`; + } + + if (volume?.data) { + output += `*Trade Data:*\n`; + output += `- Holders: ${volume.data.holder}\n`; + output += `- Unique Wallets (24h): ${volume.data.unique_wallet_24h}\n`; + output += `- Price Change (24h): ${volume.data.price_change_24h_percent}%\n`; + output += `- Price Change (12h): ${volume.data.price_change_12h_percent}%\n`; + output += `- Volume (24h USD): $${volume.data.volume_24h_usd}\n`; + output += `- Current Price: $${volume.data.price}\n\n`; + } + + if (metadata) { + output += `*Additional Info:*\n`; + output += `- Name: ${metadata.name}\n`; + output += `- Chain: ${metadata.chain}\n`; + if (metadata.website) output += `- Website: ${metadata.website}\n`; + if (metadata.twitter) output += `- Twitter: ${metadata.twitter}\n`; + } + + return output; +}; + +export const searchTokensBySymbolAction = { + name: "SEARCH_TOKENS_BY_SYMBOL", + similes: [ + "FIND_TOKENS", + "TOKEN_SEARCH", + "LOOKUP_TOKENS", + "CHECK_TOKEN", + "REVIEW_TOKEN", + "TOKEN_DETAILS", + ], + description: + "Search for detailed token information including security and trade data by symbol", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: any, + callback?: any + ) => { + try { + const provider = new BirdeyeProvider(runtime.cacheManager); + + const symbols = await extractTokenSymbolsFromMessage( + runtime, + message, + state + ); + + if (symbols.length === 0) { + callback?.({ text: "No token symbols found in the message" }); + return true; + } + + elizaLogger.info( + `Searching Birdeye provider for ${symbols.length} symbols` + ); + + const searchTokenResponses = symbols.map((symbol) => + provider.fetchSearchTokens({ + keyword: symbol, + limit: 1, + }) + ); + + const results = await Promise.all(searchTokenResponses); + const validResults = results.map( + (r, index) => + r.data.items.find( + (item) => + item.type === "token" && + item.result[0].symbol.toLowerCase() === + symbols[index].toLowerCase() + )?.result[0] + ); + + if (validResults.length === 0) { + callback?.({ text: "No matching tokens found" }); + return true; + } + + const resultsWithChains = validResults.map((result) => ({ + symbol: result.symbol, + address: result.address, + chain: extractChain(result.address), + })); + + // Fetch all data in parallel for each token + const tokenData = await Promise.all( + resultsWithChains.map(async ({ address, chain, symbol }) => { + const [metadata, security, volume] = await Promise.all([ + provider.fetchTokenMetadata(address, chain), + provider.fetchTokenSecurityBySymbol(symbol), + provider.fetchTokenTradeDataBySymbol(symbol), + ]); + return { metadata, security, volume }; + }) + ); + + const completeResults = `Found the following token information:\n\n${validResults + .map( + (result, index) => + `${formatTokenReport( + result!, + tokenData[index].metadata, + tokenData[index].security, + tokenData[index].volume + )}` + ) + .join("\n\n")}`; + + callback?.({ text: completeResults }); + return true; + } catch (error) { + console.error("Error in searchTokens handler:", error.message); + callback?.({ text: `Error: ${error.message}` }); + return false; + } + }, + validate: async (_runtime: IAgentRuntime, message: Memory) => { + const symbols = extractSymbols(message.content.text, "loose"); + return symbols.length > 0; + }, + examples: [ + [ + { + user: "user", + content: { + text: "Search for $SOL and $ETH", + action: "SEARCH_TOKENS", + }, + }, + { + user: "user", + content: { + text: "Find information about $BTC", + action: "TOKEN_SEARCH", + }, + }, + { + user: "user", + content: { + text: "Look up $WETH token", + action: "LOOKUP_TOKENS", + }, + }, + { + user: "user", + content: { + text: "Tell me about SOL", + action: "CHECK_TOKEN", + }, + }, + ], + ] as ActionExample[][], +} as Action; diff --git a/packages/plugin-birdeye/src/actions/searchWallets.ts b/packages/plugin-birdeye/src/actions/searchWallets.ts new file mode 100644 index 0000000000..6118973973 --- /dev/null +++ b/packages/plugin-birdeye/src/actions/searchWallets.ts @@ -0,0 +1,169 @@ +import { + Action, + ActionExample, + composeContext, + elizaLogger, + generateText, + ModelClass, + type IAgentRuntime, + type Memory, + type State, +} from "@elizaos/core"; +import { BirdeyeProvider } from "../birdeye"; +import { extractAddressesFromString } from "../utils"; + +const extractWalletAddressTemplate = `Given the recent message below: +{{recentMessages}} +Extract all wallet addresses mentioned in the message. Look for: +- Ethereum-style addresses (0x...) +- Solana-style addresses (base58 encoded) +- Any addresses prefixed with "wallet" or "address" + +For each address found, determine the chain (ethereum, solana) based on format. +Respond with a JSON array of objects containing the address and chain, no extra description needed. +Example: +Message: "Check wallet 0x742d35Cc6634C0532925a3b844Bc454e4438f44e and 5oGQxNmx4VrNHrq3mkZvpBzwTjnHHzxwpP1kPaS6cNyu" +Response: [ + {"address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "chain": "ethereum"}, + {"address": "5oGQxNmx4VrNHrq3mkZvpBzwTjnHHzxwpP1kPaS6cNyu", "chain": "solana"} +]`; + +const extractWalletAddressesFromMessage = async ( + runtime: IAgentRuntime, + message: Memory, + state: State +) => { + const context = composeContext({ + state, + template: extractWalletAddressTemplate, + }); + + const response = await generateText({ + runtime, + context, + modelClass: ModelClass.LARGE, + }); + + try { + const regex = new RegExp(/\[(.+)\]/gms); + const normalized = response && regex.exec(response)?.[0]; + return normalized ? JSON.parse(normalized) : []; + } catch { + return []; + } +}; + +export const searchWalletsAction = { + name: "SEARCH_WALLETS", + similes: ["CHECK_WALLET", "WALLET_HOLDINGS", "PORTFOLIO_CHECK"], + description: "Check wallet portfolio and holdings information", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: any, + callback?: any + ) => { + try { + const provider = new BirdeyeProvider(runtime.cacheManager); + + // Use the new extraction method + const addresses = await extractWalletAddressesFromMessage( + runtime, + message, + state + ); + + if (addresses.length === 0) { + callback?.({ + text: "No wallet addresses found in the message", + }); + return true; + } + + elizaLogger.info( + `Searching Birdeye provider for ${addresses.length} wallet addresses` + ); + + // Search Birdeye services for wallet portfolio data + const searchAddressesForTokenMatch = addresses.map((address) => + provider.fetchSearchWallets({ + wallet: address.address, + chain: address.chain, + }) + ); + + const results = await Promise.all(searchAddressesForTokenMatch); + const validResults = results.filter((r) => r !== null); + + elizaLogger.info( + `Found ${validResults.length} valid results for ${addresses.length} addresses` + ); + + if (validResults.length === 0) { + callback?.({ + text: "No portfolio data found for the provided addresses", + }); + return true; + } + + // Format portfolio data into readable text + const portfolioText = validResults + .map((wallet, index) => { + const tokens = wallet.data.items.slice(0, 5) || []; + const totalValue = tokens.reduce( + (sum, token) => sum + (token.valueUsd || 0), + 0 + ); + + const header = `*Wallet ${addresses[index].address}*\nTotal Value: $${totalValue.toLocaleString()}\n\nTop Holdings:`; + const tokenList = tokens + .map( + (token) => + `ā€¢ ${token.symbol}: $${token.valueUsd?.toLocaleString()} (${token.uiAmount?.toFixed(4)} tokens)` + ) + .join("\n"); + + return `${header}\n${tokenList}`; + }) + .join("\n\n"); + + callback?.({ text: portfolioText }); + return true; + } catch (error) { + console.error("Error in walletPortfolio handler:", error.message); + callback?.({ text: `Error: ${error.message}` }); + return false; + } + }, + validate: async (_runtime: IAgentRuntime, message: Memory) => { + // Check if the message contains any potential wallet addresses + const addresses = extractAddressesFromString(message.content.text); + return addresses.length > 0; + }, + examples: [ + [ + { + user: "user", + content: { + text: "Show me the portfolio for 0x1234...5678", + action: "WALLET_PORTFOLIO", + }, + }, + { + user: "user", + content: { + text: "What tokens does wallet 0xabcd...efgh hold?", + action: "CHECK_WALLET", + }, + }, + { + user: "user", + content: { + text: "Check holdings in 0x9876...5432", + action: "WALLET_HOLDINGS", + }, + }, + ], + ] as ActionExample[][], +} as Action; diff --git a/packages/plugin-birdeye/src/actions/test-all-endpoints.ts b/packages/plugin-birdeye/src/actions/test-all-endpoints.ts new file mode 100644 index 0000000000..b8dbb9ef57 --- /dev/null +++ b/packages/plugin-birdeye/src/actions/test-all-endpoints.ts @@ -0,0 +1,331 @@ +import { + Action, + ActionExample, + elizaLogger, + type IAgentRuntime, + type Memory, + type State, +} from "@elizaos/core"; +import { BirdeyeProvider } from "../birdeye"; +import { waitFor } from "../utils"; + +export const testAllEndpointsAction = { + name: "BIRDEYE_TEST_ALL_ENDPOINTS", + similes: [], + description: "Test all Birdeye endpoints with sample data", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + options: any, + callback?: any + ) => { + try { + elizaLogger.info("Testing all endpoints"); + + await waitFor(1000); + + const birdeyeProvider = new BirdeyeProvider(runtime.cacheManager); + + // Sample data for testing + const sampleParams = { + token: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm", + address: "MfDuWeqSHEqTFVYZ7LoexgAK9dxk7cy4DFJWjWMGVWa", + network: "solana", + headers: { "x-chain": "solana" }, + list_address: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm", + address_type: "token", + type: "1D", + unixtime: 1234567890, + base_address: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm", + quote_address: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm", + time_to: 1672531199, // Unix timestamp + meme_platform_enabled: true, + time_frame: "1D", + sort_type: undefined, + sort_by: undefined, + list_addresses: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm", + wallet: "MfDuWeqSHEqTFVYZ7LoexgAK9dxk7cy4DFJWjWMGVWa", + token_address: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm", + pair: "samplePair", + }; + + // Test each fetch function + await birdeyeProvider.fetchDefiSupportedNetworks(); + elizaLogger.success("fetchDefiSupportedNetworks: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchDefiPrice({ ...sampleParams }); + elizaLogger.success("fetchDefiPrice: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchDefiPriceMultiple({ ...sampleParams }); + elizaLogger.success("fetchDefiPriceMultiple: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchDefiPriceMultiple_POST({ + ...sampleParams, + }); + elizaLogger.success("fetchDefiPriceMultiple_POST: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchDefiPriceHistorical({ + ...sampleParams, + address_type: "token", + type: "1D", + }); + elizaLogger.success("fetchDefiPriceHistorical: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchDefiPriceHistoricalByUnixTime({ + ...sampleParams, + }); + elizaLogger.success("fetchDefiPriceHistoricalByUnixTime: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchDefiTradesToken({ ...sampleParams }); + elizaLogger.success("fetchDefiTradesToken: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchDefiTradesPair({ ...sampleParams }); + elizaLogger.success("fetchDefiTradesPair: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchDefiTradesTokenSeekByTime({ + ...sampleParams, + }); + elizaLogger.success("fetchDefiTradesTokenSeekByTime: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchDefiTradesPairSeekByTime({ + ...sampleParams, + }); + elizaLogger.success("fetchDefiTradesPairSeekByTime: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchDefiOHLCV({ + ...sampleParams, + type: "1D", + }); + elizaLogger.success("fetchDefiOHLCV: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchDefiOHLCVPair({ + ...sampleParams, + type: "1D", + }); + elizaLogger.success("fetchDefiOHLCVPair: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchDefiOHLCVBaseQuote({ + ...sampleParams, + type: "1D", + }); + elizaLogger.success("fetchDefiOHLCVBaseQuote: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchDefiPriceVolume({ + ...sampleParams, + type: "1D", + }); + elizaLogger.success("fetchDefiPriceVolume: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchDefiPriceVolumeMulti_POST({ + ...sampleParams, + type: "1D", + }); + elizaLogger.success("fetchDefiPriceVolumeMulti_POST: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTokenList({ + ...sampleParams, + sort_by: "mc", + sort_type: "desc", + }); + elizaLogger.success("fetchTokenList: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTokenSecurityByAddress({ + ...sampleParams, + }); + elizaLogger.success("fetchTokenSecurityByAddress: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTokenOverview({ ...sampleParams }); + elizaLogger.success("fetchTokenOverview: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTokenCreationInfo({ ...sampleParams }); + elizaLogger.success("fetchTokenCreationInfo: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTokenTrending({ + ...sampleParams, + sort_by: "volume24hUSD", + sort_type: "desc", + }); + elizaLogger.success("fetchTokenTrending: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTokenListV2_POST({}); + elizaLogger.success("fetchTokenListV2_POST: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTokenNewListing({ ...sampleParams }); + elizaLogger.success("fetchTokenNewListing: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTokenTopTraders({ + ...sampleParams, + time_frame: "12H", + sort_type: "asc", + sort_by: "volume", + }); + elizaLogger.success("fetchTokenTopTraders: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTokenAllMarketsList({ + ...sampleParams, + time_frame: "12H", + sort_type: "asc", + sort_by: "volume24h", + }); + elizaLogger.success("fetchTokenAllMarketsList: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTokenMetadataSingle({ ...sampleParams }); + elizaLogger.success("fetchTokenMetadataSingle: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTokenMetadataMulti({ ...sampleParams }); + elizaLogger.success("fetchTokenMetadataMulti: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTokenMarketData({ ...sampleParams }); + elizaLogger.success("fetchTokenMarketData: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTokenTradeDataSingle({ + ...sampleParams, + }); + elizaLogger.success("fetchTokenTradeDataSingle: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTokenTradeDataMultiple({ + ...sampleParams, + }); + elizaLogger.success("fetchTokenTradeDataMultiple: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTokenHolders({ ...sampleParams }); + elizaLogger.success("fetchTokenHolders: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTokenMintBurn({ + ...sampleParams, + sort_by: "block_time", + sort_type: "desc", + type: "all", + }); + elizaLogger.success("fetchTokenMintBurn: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchWalletSupportedNetworks(); + elizaLogger.success("fetchWalletSupportedNetworks: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchWalletPortfolio({ ...sampleParams }); + elizaLogger.success("fetchWalletPortfolio: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchWalletPortfolioMultichain({ + ...sampleParams, + }); + elizaLogger.success("fetchWalletPortfolioMultichain: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchWalletTokenBalance({ ...sampleParams }); + elizaLogger.success("fetchWalletTokenBalance: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchWalletTransactionHistory({ + ...sampleParams, + }); + elizaLogger.success("fetchWalletTransactionHistory: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchWalletTransactionHistoryMultichain({ + ...sampleParams, + }); + elizaLogger.success( + "fetchWalletTransactionHistoryMultichain: SUCCESS!" + ); + await waitFor(500); + + await birdeyeProvider.fetchWalletTransactionSimulate_POST({ + ...sampleParams, + }); + elizaLogger.success( + "fetchWalletTransactionSimulate_POST: SUCCESS!" + ); + await waitFor(500); + + await birdeyeProvider.fetchTraderGainersLosers({ + ...sampleParams, + type: "today", + }); + elizaLogger.success("fetchTraderGainersLosers: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchTraderTransactionsSeek({ + ...sampleParams, + }); + elizaLogger.success("fetchTraderTransactionsSeek: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchPairOverviewSingle({ ...sampleParams }); + elizaLogger.success("fetchPairOverviewSingle: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchMultiPairOverview({ ...sampleParams }); + elizaLogger.success("fetchMultiPairOverview: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchPairOverviewMultiple({ + ...sampleParams, + }); + elizaLogger.success("fetchPairOverviewMultiple: SUCCESS!"); + await waitFor(500); + + await birdeyeProvider.fetchSearchTokenMarketData({ + ...sampleParams, + }); + elizaLogger.success("fetchSearchTokenMarketData: SUCCESS!"); + await waitFor(500); + + elizaLogger.info("All endpoints tested successfully"); + callback?.({ text: "All endpoints tested successfully" }); + return true; + } catch (error) { + console.error("Error in testAllEndpointsAction:", error.message); + callback?.({ text: `Error: ${error.message}` }); + return false; + } + }, + validate: async (_runtime: IAgentRuntime, message: Memory) => { + // only run if explicitly triggered by user + return message.content.text.includes("BIRDEYE_TEST_ALL_ENDPOINTS"); + }, + examples: [ + [ + { + user: "user", + content: { + text: "I want you to BIRDEYE_TEST_ALL_ENDPOINTS", + action: "BIRDEYE_TEST_ALL_ENDPOINTS", + }, + }, + ], + ] as ActionExample[][], +} as Action; diff --git a/packages/plugin-birdeye/src/birdeye.ts b/packages/plugin-birdeye/src/birdeye.ts index 6c34331cf1..4f911e029a 100644 --- a/packages/plugin-birdeye/src/birdeye.ts +++ b/packages/plugin-birdeye/src/birdeye.ts @@ -1,18 +1,108 @@ -import { elizaLogger, ICacheManager, settings } from "@elizaos/core"; +import { ICacheManager, settings } from "@elizaos/core"; import NodeCache from "node-cache"; import * as path from "path"; import { API_BASE_URL, + BIRDEYE_ENDPOINTS, DEFAULT_MAX_RETRIES, DEFAULT_SUPPORTED_SYMBOLS, - ENDPOINT_MAP, RETRY_DELAY_MS, } from "./constants"; -import { SearchTokenResponse, SearchTokensOptions } from "./types/search-token"; -import { BirdeyeChain } from "./types/shared"; -import { TokenMetadataResponse } from "./types/token-metadata"; -import { WalletDataOptions, WalletDataResponse } from "./types/wallet"; -import { waitFor } from "./utils"; +import { BirdeyeApiParams, BirdeyeApiResponse } from "./types/api/common"; +import { + BaseQuoteParams, + BaseQuoteResponse, + DefiHistoryPriceParams, + DefiHistoryPriceResponse, + DefiMultiPriceParams, + DefiMultiPriceParamsPOST, + DefiMultiPriceResponse, + DefiNetworksResponse, + DefiPriceParams, + DefiPriceResponse, + DefiTradesTokenParams, + DefiTradesTokenResponse, + HistoricalPriceUnixParams, + HistoricalPriceUnixResponse, + MultiPriceVolumeParams, + MultiPriceVolumeResponse, + OHLCVParams, + OHLCVResponse, + PriceVolumeParams, + PriceVolumeResponse, +} from "./types/api/defi"; +import { + OHLCVPairParams, + OHLCVPairResponse, + PairOverviewMultiParams, + PairOverviewMultiResponse, + PairOverviewSingleParams, + PairOverviewSingleResponse, + PairTradesParams, + PairTradesResponse, +} from "./types/api/pair"; +import { + TokenMarketSearchParams, + TokenMarketSearchResponse, +} from "./types/api/search"; +import { + AllMarketsParams, + AllMarketsResponse, + MintBurnParams, + MintBurnResponse, + NewListingParams, + NewListingResponse, + TokenCreationInfoParams, + TokenCreationInfoResponse, + TokenHoldersParams, + TokenHoldersResponse, + TokenListParams, + TokenListResponse, + TokenListV2Response, + TokenMarketDataParams, + TokenMarketDataResponse, + TokenMetadataMultiParams, + TokenMetadataMultiResponse, + TokenMetadataSingleParams, + TokenMetadataSingleResponse, + TokenOverviewParams, + TokenOverviewResponse, + TokenSecurityParams, + TokenSecurityResponse, + TokenTradeDataMultiParams, + TokenTradeDataMultiResponse, + TokenTradeDataSingleParams, + TokenTradeDataSingleResponse, + TokenTrendingParams, + TokenTrendingResponse, + TopTradersParams, + TopTradersResponse, +} from "./types/api/token"; +import { + GainersLosersParams, + GainersLosersResponse, + TraderTransactionsSeekParams, + TraderTransactionsSeekResponse, +} from "./types/api/trader"; +import { + WalletPortfolioMultichainParams, + WalletPortfolioMultichainResponse, + WalletPortfolioParams, + WalletPortfolioResponse, + WalletSimulationParams, + WalletSimulationResponse, + WalletTokenBalanceParams, + WalletTokenBalanceResponse, + WalletTransactionHistoryMultichainParams, + WalletTransactionHistoryMultichainResponse, + WalletTransactionHistoryParams, + WalletTransactionHistoryResponse, +} from "./types/api/wallet"; +import { convertToStringParams, waitFor } from "./utils"; + +type FetchParams = T & { + headers?: Record; +}; class BaseCachedProvider { private cache: NodeCache; @@ -74,32 +164,19 @@ export class BirdeyeProvider extends BaseCachedProvider { this.maxRetries = maxRetries || DEFAULT_MAX_RETRIES; } - public getTokenAddress(symbol: string) { - const addr = this.symbolMap[symbol]; - - if (!addr) { - throw new Error(`Unsupported symbol ${symbol} in Birdeye provider`); - } - - return addr; - } - - private getUrlByType(type: string, address?: string) { - const path = ENDPOINT_MAP[type]; - - if (!path) { - throw new Error(`Unsupported symbol ${type} in Birdeye provider`); - } - - return `${API_BASE_URL}${path}${address || ""}`; - } - - private async fetchWithRetry( + /* + * COMMON FETCH FUNCTIONS + */ + private async fetchWithRetry( url: string, options: RequestInit = {} - ): Promise { + ): Promise { let attempts = 0; + // allow the user to override the chain + const chain = + options.headers?.["x-chain"] || settings.BIRDEYE_CHAIN || "solana"; + while (attempts < this.maxRetries) { attempts++; try { @@ -107,7 +184,8 @@ export class BirdeyeProvider extends BaseCachedProvider { ...options, headers: { Accept: "application/json", - "x-chain": settings.BIRDEYE_CHAIN || "solana", + "Content-Type": "application/json", + "x-chain": chain, "X-API-KEY": settings.BIRDEYE_API_KEY || "", ...options.headers, }, @@ -120,8 +198,19 @@ export class BirdeyeProvider extends BaseCachedProvider { ); } - const data = await resp.json(); - return data; + const rawData = await resp.json(); + // If the response already has data and success fields, return it + if ( + rawData.data !== undefined && + rawData.success !== undefined + ) { + return rawData as T; + } + // Otherwise wrap the response in the expected format + return { + data: rawData, + success: true, + } as T; } catch (error) { if (attempts === this.maxRetries) { // failed after all @@ -132,147 +221,518 @@ export class BirdeyeProvider extends BaseCachedProvider { } } - private async fetchWithCacheAndRetry( - type: string, - address?: string - ): Promise { - const key = `${type}/${address}`; - const val = await this.readFromCache(key); + private async fetchWithCacheAndRetry({ + url, + params, + headers, + method = "GET", + }: { + url: string; + params?: BirdeyeApiParams; + headers?: Record; + method?: "GET" | "POST"; + }): Promise { + const stringParams = convertToStringParams(params); + const fullUrl = `${API_BASE_URL}${url}`; + const cacheKey = + method === "GET" + ? `${url}?${new URLSearchParams(stringParams)}` + : `${url}:${JSON.stringify(params)}`; + + const val = await this.readFromCache(cacheKey); + if (val) return val as T; + + const data = await this.fetchWithRetry( + method === "GET" && params + ? `${fullUrl}?${new URLSearchParams(stringParams)}` + : fullUrl, + { + method, + headers, + ...(method === "POST" && + params && { body: JSON.stringify(params) }), + } + ); - if (val) { - return val; - } + await this.writeToCache(cacheKey, data); + return data as T; + } - const url = this.getUrlByType(type, address); - const data = await this.fetchWithRetry(url); + /* + * DEFI FETCH FUNCTIONS + */ - await this.writeToCache(key, data); - return data; + // Get a list of all supported networks. + public async fetchDefiSupportedNetworks() { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.networks, + }); } - public async fetchTokenList() { - return this.fetchWithCacheAndRetry("tokens"); + // Get price update of a token. + public async fetchDefiPrice(params: FetchParams) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.price, + params, + headers: params.headers, + }); } - public async fetchPriceBySymbol(symbol: string) { - return this.fetchPriceByAddress(this.getTokenAddress(symbol)); + // Get price updates of multiple tokens in a single API call. Maximum 100 tokens + public async fetchDefiPriceMultiple( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.price_multi, + params, + headers: params.headers, + }); } - public async fetchPriceByAddress(address: string) { - return this.fetchWithCacheAndRetry("price", address); + + // Get price updates of multiple tokens in a single API call. Maximum 100 tokens + public async fetchDefiPriceMultiple_POST( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.price_multi_POST, + params, + headers: params.headers, + method: "POST", + }); } - public async fetchTokenSecurityBySymbol(symbol: string) { - return this.fetchTokenSecurityByAddress(this.getTokenAddress(symbol)); + // Get historical price line chart of a token. + public async fetchDefiPriceHistorical( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.history_price, + params, + headers: params.headers, + }); } - public async fetchTokenSecurityByAddress(address: string) { - return this.fetchWithCacheAndRetry("security", address); + + // Get historical price by unix timestamp + public async fetchDefiPriceHistoricalByUnixTime( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.historical_price_unix, + params, + headers: params.headers, + }); } - public async fetchTokenTradeDataBySymbol(symbol: string) { - return this.fetchTokenTradeDataByAddress(this.getTokenAddress(symbol)); + // Get list of trades of a certain token. + public async fetchDefiTradesToken( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.trades_token, + params, + headers: params.headers, + }); } - public async fetchTokenTradeDataByAddress(address: string) { - return this.fetchWithCacheAndRetry("volume", address); + + // Get list of trades of a certain pair or market. + public async fetchDefiTradesPair( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.trades_token, + params, + headers: params.headers, + }); } - public async fetchWalletPortfolio(address: string) { - return this.fetchWithCacheAndRetry("portfolio", address); + // Get list of trades of a token with time bound option. + public async fetchDefiTradesTokenSeekByTime( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.trades_token_seek, + params, + headers: params.headers, + }); } - /** - * Fetches token data for a given keyword and chain. - * @param options - The options for the token data. - * @returns The token data. + // Get list of trades of a certain pair or market with time bound option. + public async fetchDefiTradesPairSeekByTime( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.trades_pair_seek, + params, + headers: params.headers, + }); + } + + // Get OHLCV price of a token. + public async fetchDefiOHLCV(params: FetchParams) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.ohlcv, + params, + headers: params.headers, + }); + } + + // Get OHLCV price of a pair. + public async fetchDefiOHLCVPair(params: FetchParams) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.ohlcv_pair, + params, + headers: params.headers, + }); + } + + // Get OHLCV price of a base-quote pair. + public async fetchDefiOHLCVBaseQuote(params: FetchParams) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.ohlcv_base_quote, + params, + headers: params.headers, + }); + } + + // Get price and volume of a token. + public async fetchDefiPriceVolume(params: FetchParams) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.price_volume, + params, + headers: params.headers, + }); + } + + // Get price and volume updates of maximum 50 tokens + public async fetchDefiPriceVolumeMulti_POST( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.price_volume_multi_POST, + params, + headers: params.headers, + method: "POST", + }); + } + + /* + * TOKEN FETCH FUNCTIONS */ - public async fetchSearchTokens( - options: SearchTokensOptions - ): Promise { - const { keyword, chain = "all", limit = 1, offset = 0 } = options; - const params = new URLSearchParams({ - keyword, - limit: limit.toString(), - offset: offset.toString(), - chain: chain, + + // Get token list of any supported chains. + public async fetchTokenList(params: FetchParams) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.token.list_all, + params, + headers: params.headers, + }); + } + + // Get token security of any supported chains. + public async fetchTokenSecurityByAddress( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.token.security, + params, + headers: params.headers, }); + } - const url = `${API_BASE_URL}/defi/v3/search?${params.toString()}`; - const response: SearchTokenResponse = await this.fetchWithRetry(url); - return response; + // Get overview of a token. + public async fetchTokenOverview(params: FetchParams) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.token.overview, + params, + headers: params.headers, + }); } - /** - * Fetches wallet portfolio data for a given wallet address and chains. - * @param options - The options for the wallet portfolio data. - * @returns The wallet portfolio data. + // Get creation info of token + public async fetchTokenCreationInfo( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.token.creation_info, + params, + headers: params.headers, + }); + } + + // Retrieve a dynamic and up-to-date list of trending tokens based on specified sorting criteria. + public async fetchTokenTrending(params?: FetchParams) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.token.trending, + params, + headers: params?.headers, + }); + } + + // This endpoint facilitates the retrieval of a list of tokens on a specified blockchain network. This upgraded version is exclusive to business and enterprise packages. By simply including the header for the requested blockchain without any query parameters, business and enterprise users can get the full list of tokens on the specified blockchain in the URL returned in the response. This removes the need for the limit response of the previous version and reduces the workload of making multiple calls. + public async fetchTokenListV2_POST( + params: FetchParams> + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.token.list_all_v2_POST, + params, + headers: params.headers, + method: "POST", + }); + } + + // Get newly listed tokens of any supported chains. + public async fetchTokenNewListing(params?: FetchParams) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.token.new_listing, + params, + headers: params?.headers, + }); + } + + // Get top traders of given token. + public async fetchTokenTopTraders(params: FetchParams) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.token.top_traders, + params, + headers: params.headers, + }); + } + + // The API provides detailed information about the markets for a specific cryptocurrency token on a specified blockchain. Users can retrieve data for one or multiple markets related to a single token. This endpoint requires the specification of a token address and the blockchain to filter results. Additionally, it supports optional query parameters such as offset, limit, and required sorting by liquidity or sort type (ascending or descending) to refine the output. + public async fetchTokenAllMarketsList( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.token.all_markets, + params, + headers: params.headers, + }); + } + + // Get metadata of single token + public async fetchTokenMetadataSingle( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.token.metadata_single, + params, + headers: params.headers, + }); + } + + // Get metadata of multiple tokens + public async fetchTokenMetadataMulti( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.token.metadata_multi, + params, + headers: params.headers, + }); + } + + // Get market data of single token + public async fetchTokenMarketData( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.token.market_data, + params, + headers: params.headers, + }); + } + + // Get trade data of single token + public async fetchTokenTradeDataSingle( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.token.trade_data_single, + params, + headers: params.headers, + }); + } + + // Get trade data of multiple tokens + public async fetchTokenTradeDataMultiple( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.token.trade_data_multi, + params, + headers: params.headers, + }); + } + + // Get top holder list of the given token + public async fetchTokenHolders(params: FetchParams) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.token.holders, + params, + headers: params.headers, + }); + } + + // Get mint/burn transaction list of the given token. Only support solana currently + public async fetchTokenMintBurn(params: FetchParams) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.token.mint_burn, + params, + headers: params.headers, + }); + } + + /* + * WALLET FETCH FUNCTIONS */ - public async fetchSearchWallets( - options: WalletDataOptions - ): Promise { - const { wallet, chain } = options; + public async fetchWalletSupportedNetworks( + params?: FetchParams> + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.networks, + headers: params?.headers, + }); + } + + public async fetchWalletPortfolio( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.wallet.portfolio, + params, + headers: params.headers, + }); + } - const params = new URLSearchParams({ - wallet, + /** + * @deprecated This endpoint will be decommissioned on Feb 1st, 2025. + */ + public async fetchWalletPortfolioMultichain( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.wallet.portfolio_multichain, + params, + headers: params.headers, }); + } - const url = `${API_BASE_URL}/v1/wallet/token_list?${params.toString()}`; - const response = await this.fetchWithRetry(url, { - headers: { "x-chain": chain }, + public async fetchWalletTokenBalance( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.wallet.token_balance, + params, + headers: params.headers, }); + } - return response; + public async fetchWalletTransactionHistory( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.wallet.transaction_history, + params, + headers: params.headers, + }); } /** - * Fetches token metadata for a given address and chain. - * @param address - The address of the token. - * @param chain - The chain of the token. - * @returns The token metadata. + * @deprecated This endpoint will be decommissioned on Feb 1st, 2025. */ - public async fetchTokenMetadata( - address: string, - chain: BirdeyeChain - ): Promise { - const isValidAddress = (() => { - switch (chain) { - case "solana": - return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address); - case "sui": - return /^0x[a-fA-F0-9]{64}$/i.test(address); - case "ethereum": - case "arbitrum": - case "avalanche": - case "bsc": - case "optimism": - case "polygon": - case "base": - case "zksync": - return /^0x[a-fA-F0-9]{40}$/i.test(address); - default: - return false; + public async fetchWalletTransactionHistoryMultichain( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry( + { + url: BIRDEYE_ENDPOINTS.wallet.transaction_history_multichain, + params, + headers: params.headers, } - })(); + ); + } - if (!isValidAddress) { - elizaLogger.error( - `Invalid address format for ${chain}: ${address}` - ); - return null; - } + public async fetchWalletTransactionSimulate_POST( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.wallet.transaction_simulation_POST, + params, + headers: params.headers, + method: "POST", + }); + } - const params = new URLSearchParams({ address }); - const url = `${API_BASE_URL}/defi/v3/token/meta-data/single?${params.toString()}`; + /* + * TRADER FETCH FUNCTIONS + */ - const response: TokenMetadataResponse = await this.fetchWithRetry(url, { - headers: { "x-chain": chain }, - }).catch(() => null); + // The API provides detailed information top gainers/losers + public async fetchTraderGainersLosers( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.trader.gainers_losers, + params, + headers: params.headers, + }); + } - if (!response) { - elizaLogger.error( - `Failed to fetch token metadata for ${address} on ${chain}` - ); - return null; - } + // Get list of trades of a trader with time bound option. + public async fetchTraderTransactionsSeek( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.trader.trades_seek, + params, + headers: params.headers, + }); + } + + /* + * PAIR FETCH FUNCTIONS + */ + public async fetchPairOverviewSingle( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.pair.overview_single, + params, + headers: params.headers, + }); + } + + // Get overview of multiple pairs + public async fetchMultiPairOverview( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.pair.overview_multi, + params, + headers: params.headers, + }); + } + + public async fetchPairOverviewMultiple( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.defi.trades_pair, + params, + headers: params.headers, + }); + } - return response; + /* + * SEARCH FETCH FUNCTIONS + */ + public async fetchSearchTokenMarketData( + params: FetchParams + ) { + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.search.token_market, + params, + headers: params.headers, + }); } } diff --git a/packages/plugin-birdeye/src/constants.ts b/packages/plugin-birdeye/src/constants.ts index ff20e16d59..f598698c8c 100644 --- a/packages/plugin-birdeye/src/constants.ts +++ b/packages/plugin-birdeye/src/constants.ts @@ -8,12 +8,72 @@ export const DEFAULT_SUPPORTED_SYMBOLS = { }; export const API_BASE_URL = "https://public-api.birdeye.so"; -export const ENDPOINT_MAP = { - price: "/defi/price?address=", - security: "/defi/token_security?address=", - volume: "/defi/v3/token/trade-data/single?address=", - portfolio: "/v1/wallet/token_list?wallet=", - tokens: "/defi/tokenlist", -}; export const RETRY_DELAY_MS = 2_000; + +export const BIRDEYE_ENDPOINTS = { + defi: { + networks: "/defi/networks", // https://docs.birdeye.so/reference/get_defi-networks + price: "/defi/price", // https://docs.birdeye.so/reference/get_defi-price + price_multi: "/defi/multi_price", // https://docs.birdeye.so/reference/get_defi-multi-price + price_multi_POST: "/defi/multi_price", // https://docs.birdeye.so/reference/post_defi-multi-price + history_price: "/defi/history_price", // https://docs.birdeye.so/reference/get_defi-history-price + historical_price_unix: "/defi/historical_price_unix", // https://docs.birdeye.so/reference/get_defi-historical-price-unix + trades_token: "/defi/txs/token", // https://docs.birdeye.so/reference/get_defi-txs-token + trades_pair: "/defi/txs/pair", // https://docs.birdeye.so/reference/get_defi-txs-pair + trades_token_seek: "/defi/txs/token/seek_by_time", // https://docs.birdeye.so/reference/get_defi-txs-token-seek-by-time + trades_pair_seek: "/defi/txs/pair/seek_by_time", // https://docs.birdeye.so/reference/get_defi-txs-pair-seek-by-time + ohlcv: "/defi/ohlcv", // https://docs.birdeye.so/reference/get_defi-ohlcv + ohlcv_pair: "/defi/ohlcv/pair", // https://docs.birdeye.so/reference/get_defi-ohlcv-pair + ohlcv_base_quote: "/defi/ohlcv/base_quote", // https://docs.birdeye.so/reference/get_defi-ohlcv-base-quote + price_volume: "/defi/price_volume/single", // https://docs.birdeye.so/reference/get_defi-price-volume-single + price_volume_multi: "/defi/price_volume/multi", // https://docs.birdeye.so/reference/get_defi-price-volume-multi + price_volume_multi_POST: "/defi/price_volume/multi", // https://docs.birdeye.so/reference/post_defi-price-volume-multi + }, + token: { + list_all: "/defi/tokenlist", // https://docs.birdeye.so/reference/get_defi-tokenlist + security: "/defi/token_security", // https://docs.birdeye.so/reference/get_defi-token-security + overview: "/defi/token_overview", // https://docs.birdeye.so/reference/get_defi-token-overview + creation_info: "/defi/token_creation_info", // https://docs.birdeye.so/reference/get_defi-token-creation-info + trending: "/defi/token_trending", // https://docs.birdeye.so/reference/get_defi-token-trending + list_all_v2_POST: "/defi/v2/tokens/all", // https://docs.birdeye.so/reference/post_defi-v2-tokens-all + new_listing: "/defi/v2/tokens/new_listing", // https://docs.birdeye.so/reference/get_defi-v2-tokens-new-listing + top_traders: "/defi/v2/tokens/top_traders", // https://docs.birdeye.so/reference/get_defi-v2-tokens-top-traders + all_markets: "/defi/v2/markets", // https://docs.birdeye.so/reference/get_defi-v2-markets + metadata_single: "/defi/v3/token/meta-data/single", // https://docs.birdeye.so/reference/get_defi-v3-token-meta-data-single + metadata_multi: "/defi/v3/token/meta-data/multiple", // https://docs.birdeye.so/reference/get_defi-v3-token-meta-data-multiple + market_data: "/defi/v3/token/market-data", // https://docs.birdeye.so/reference/get_defi-v3-token-market-data + trade_data_single: "/defi/v3/token/trade-data/single", // https://docs.birdeye.so/reference/get_defi-v3-token-trade-data-single + trade_data_multi: "/defi/v3/token/trade-data/multiple", // https://docs.birdeye.so/reference/get_defi-v3-token-trade-data-multiple + holders: "/defi/v3/token/holder", // https://docs.birdeye.so/reference/get_defi-v3-token-holder + mint_burn: "/defi/v3/token/mint-burn-txs", // https://docs.birdeye.so/reference/get_defi-v3-token-mint-burn-txs + }, + wallet: { + networks: "/v1/wallet/list_supported_chain", // https://docs.birdeye.so/reference/get_v1-wallet-list-supported-chain + portfolio: "/v1/wallet/token_list", // https://docs.birdeye.so/reference/get_v1-wallet-token-list + portfolio_multichain: "/v1/wallet/multichain_token_list", // https://docs.birdeye.so/reference/get_v1-wallet-multichain-token-list + token_balance: "/v1/wallet/token_balance", // https://docs.birdeye.so/reference/get_v1-wallet-token-balance + transaction_history: "/v1/wallet/tx_list", // https://docs.birdeye.so/reference/get_v1-wallet-tx-list + transaction_history_multichain: "/v1/wallet/multichain_tx_list", // https://docs.birdeye.so/reference/get_v1-wallet-multichain-tx-list + transaction_simulation_POST: "/v1/wallet/simulate", // https://docs.birdeye.so/reference/post_v1-wallet-simulate + }, + trader: { + gainers_losers: "/trader/gainers_losers", // https://docs.birdeye.so/reference/get_trader-gainers-losers + trades_seek: "/trader/txs/seek_by_time", // https://docs.birdeye.so/reference/get_trader-txs-seek-by-time + }, + pair: { + overview_multi: "/defi/v3/pair/overview/multiple", // https://docs.birdeye.so/reference/get_defi-v3-pair-overview-multiple + overview_single: "/defi/v3/pair/overview/single", // https://docs.birdeye.so/reference/get_defi-v3-pair-overview-single + }, + search: { + token_market: "/defi/v3/search", // https://docs.birdeye.so/reference/get_defi-v3-search + }, +}; + +export type BirdeyeEndpoint = + | keyof (typeof BIRDEYE_ENDPOINTS)["defi"] + | keyof (typeof BIRDEYE_ENDPOINTS)["token"] + | keyof (typeof BIRDEYE_ENDPOINTS)["wallet"] + | keyof (typeof BIRDEYE_ENDPOINTS)["trader"] + | keyof (typeof BIRDEYE_ENDPOINTS)["pair"] + | keyof (typeof BIRDEYE_ENDPOINTS)["search"]; diff --git a/packages/plugin-birdeye/src/index.ts b/packages/plugin-birdeye/src/index.ts index dee6e5882a..f943bee8d4 100644 --- a/packages/plugin-birdeye/src/index.ts +++ b/packages/plugin-birdeye/src/index.ts @@ -1,29 +1,19 @@ import { Plugin } from "@elizaos/core"; -import { getSupportedNetworksAction } from "./actions/defi/networks"; -import { reportToken } from "./actions/report"; -import { addressSearchProvider } from "./providers/address-search-provider"; +import { searchTokensBySymbolAction } from "./actions/searchTokens"; +import { searchWalletsAction } from "./actions/searchWallets"; +import { testAllEndpointsAction } from "./actions/test-all-endpoints"; import { agentPortfolioProvider } from "./providers/agent-portfolio-provider"; -import { symbolSearchProvider } from "./providers/symbol-search-provider"; -import { walletPortfolioProvider } from "./providers/wallet-portfolio-provider"; export const birdeyePlugin: Plugin = { name: "birdeye", description: "Birdeye Plugin for token data and analytics", actions: [ - reportToken, - getSupportedNetworksAction, - // getTokenMetadataAction, - // getPriceHistoryAction, - // getOHLCVAction, - // getTokenTradesAction, + searchTokensBySymbolAction, + searchWalletsAction, + testAllEndpointsAction, ], evaluators: [], - providers: [ - symbolSearchProvider, - addressSearchProvider, - walletPortfolioProvider, - agentPortfolioProvider, - ], + providers: [agentPortfolioProvider], }; export default birdeyePlugin; diff --git a/packages/plugin-birdeye/src/providers/address-search-provider.ts b/packages/plugin-birdeye/src/providers/address-search-provider.ts deleted file mode 100644 index d20c819ba5..0000000000 --- a/packages/plugin-birdeye/src/providers/address-search-provider.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - elizaLogger, - IAgentRuntime, - Memory, - Provider, - State, -} from "@elizaos/core"; -import { BirdeyeProvider } from "../birdeye"; -import { - extractAddressesFromString, - extractChain, - formatTokenInfo, -} from "../utils"; - -/** - * Searches message text for ALL contract addresses, symbols, or wallet addresses and enriches them with: - * - Portfolio data if its a wallet address - * - Token metadata if its a contract address or symbol - */ -export const addressSearchProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const provider = new BirdeyeProvider(runtime.cacheManager); - - const messageText = message.content.text; - - // STEP 1 - Extract addresses and symbols - const addresses = extractAddressesFromString(messageText); - if (addresses.length === 0) return null; - - elizaLogger.info( - `Searching Birdeye provider for ${addresses.length} token addresses` - ); - - // STEP 2 - Search Birdeye services for token matches based on addresses and symbols - const searchAddressesForTokenMatch = addresses.map((address) => - provider - .fetchSearchTokens({ - keyword: address.address, - limit: 1, - }) - .then((results) => ({ - searchTerm: address.address, - address: address.address, - result: results[0] || null, - })) - ); - - const results = await Promise.all(searchAddressesForTokenMatch); - const validResults = results.filter((r) => r.result !== null); - - elizaLogger.info( - `Found ${validResults.length} valid results for ${addresses.length} addresses` - ); - - // bail if no valid results - if (validResults.length === 0) return null; - - // STEP 3 - get metadata for all valid results and format them. This includes additional token information like social links, logo, etc. - const resultsWithMetadata = await Promise.all( - validResults.map(({ address }) => - provider.fetchTokenMetadata(address, extractChain(address)) - ) - ); - - // STEP 4 - Format all results together - const completeResults = `The following data is available for the addresses requested: ${validResults - .map( - ({ searchTerm, result }, index) => - `Address "${searchTerm}":\n${formatTokenInfo(result!, resultsWithMetadata[index])}` - ) - .join("\n\n")}`; - - return completeResults; - }, -}; diff --git a/packages/plugin-birdeye/src/providers/agent-portfolio-provider.ts b/packages/plugin-birdeye/src/providers/agent-portfolio-provider.ts index cd2f2eb1b6..7ed0f99ffb 100644 --- a/packages/plugin-birdeye/src/providers/agent-portfolio-provider.ts +++ b/packages/plugin-birdeye/src/providers/agent-portfolio-provider.ts @@ -30,12 +30,12 @@ export const agentPortfolioProvider: Provider = { const chain = extractChain(walletAddr); - const resp = await provider.fetchSearchWallets({ - wallet: walletAddr, + const resp = await provider.fetchSearchTokenMarketData({ + keyword: walletAddr, chain, }); - const portfolioText = formatPortfolio(resp?.data?.items || []); + const portfolioText = formatPortfolio(resp); return `This is your wallet address: ${walletAddr}\n\nThis is your portfolio: [${portfolioText}]`; } catch (error) { diff --git a/packages/plugin-birdeye/src/providers/symbol-search-provider.ts b/packages/plugin-birdeye/src/providers/symbol-search-provider.ts deleted file mode 100644 index 11ca5b4f4e..0000000000 --- a/packages/plugin-birdeye/src/providers/symbol-search-provider.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { - elizaLogger, - IAgentRuntime, - Memory, - Provider, - State, -} from "@elizaos/core"; -import { BirdeyeProvider } from "../birdeye"; -import { extractChain, extractSymbols, formatTokenInfo } from "../utils"; - -/** - * Searches message text for ALL token symbols and then enriches them with the basic token stats and metadata from Birdeye - */ -export const symbolSearchProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - const provider = new BirdeyeProvider(runtime.cacheManager); - - const messageText = message.content.text; - - // STEP 1 - Extract symbols - const symbols = extractSymbols(messageText); - - if (symbols.length === 0) return null; - - elizaLogger.info( - `Searching Birdeye provider for ${symbols.length} symbols` - ); - - // STEP 2 - Search Birdeye services for token matches based on symbols - const searchTokenResponses = symbols.map((symbol) => - provider.fetchSearchTokens({ - keyword: symbol, - limit: 1, - }) - ); - - const results = await Promise.all(searchTokenResponses); - const validResults = results.map( - (r, index) => - r.data.items.find( - (item) => - item.type === "token" && - item.result[0].symbol.toLowerCase() === - symbols[index].toLowerCase() - )?.result[0] - ); - - elizaLogger.info( - `Found ${validResults.length} valid results for ${symbols.length} symbols` - ); - - console.log(JSON.stringify(validResults, null, 2)); - - // bail if no valid results - if (validResults.length === 0) return null; - - // for each result, get the chain from the search term - const resultsWithChains = validResults.map((result) => ({ - symbol: result.symbol, - address: result.address, - chain: extractChain(result.address), - })); - - // STEP 3 - get metadata for all valid results and format them. This includes additional token information like social links, logo, etc. - const resultsWithMetadata = await Promise.all( - resultsWithChains.map(({ address, chain }) => - provider.fetchTokenMetadata(address, chain) - ) - ); - - // STEP 4 - Format all results together - const completeResults = `The following data is available for the symbols requested: ${validResults - .map( - (result, index) => - `Search term "${result.symbol}":\n${formatTokenInfo(result!, resultsWithMetadata[index])}` - ) - .join("\n\n")}`; - - console.log(completeResults); - - return completeResults; - }, -}; diff --git a/packages/plugin-birdeye/src/providers/wallet-portfolio-provider.ts b/packages/plugin-birdeye/src/providers/wallet-portfolio-provider.ts deleted file mode 100644 index 94631c47bb..0000000000 --- a/packages/plugin-birdeye/src/providers/wallet-portfolio-provider.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { - elizaLogger, - IAgentRuntime, - Memory, - Provider, - State, -} from "@elizaos/core"; -import { BirdeyeProvider } from "../birdeye"; -import { extractAddressesFromString } from "../utils"; - -/** - * Wallet portfolio provider that queries Birdeye API for a any potential wallet addresses the message. - * When a wallet address is set, this provider fetches portfolio data to give the agent - * context about the user's top holdings when responding to queries. - */ -export const walletPortfolioProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - try { - const provider = new BirdeyeProvider(runtime.cacheManager); - - const messageText = message.content.text; - - // STEP 1 - Extract addresses and symbols - const addresses = extractAddressesFromString(messageText); - - if (addresses.length === 0) return null; - - elizaLogger.info( - `Searching Birdeye provider for ${addresses.length} wallet addresses` - ); - - // STEP 2 - Search Birdeye services for wallet portfolio data - const searchAddressesForTokenMatch = addresses.map((address) => - provider.fetchSearchWallets({ - wallet: address.address, - chain: address.chain, - }) - ); - - // STEP 3 - Format the results together - const results = await Promise.all(searchAddressesForTokenMatch); - const validResults = results.filter((r) => r !== null); - - elizaLogger.info( - `Found ${validResults.length} valid results for ${addresses.length} addresses` - ); - - if (validResults.length === 0) return null; - - // Format portfolio data into readable text - const portfolioText = validResults - .map((wallet, index) => { - const tokens = wallet.data.items.slice(0, 5) || []; - const tokenList = tokens - .map( - (token) => - `${token.symbol}: $${token.valueUsd?.toLocaleString()}` - ) - .join(", "); - - return `Wallet ${addresses[index].address} holds: ${tokenList}`; - }) - .join("\n"); - - return portfolioText; - } catch (error) { - console.error("Error fetching wallet portfolio data:", error); - return "Unable to fetch wallet portfolio information. Please try again later."; - } - }, -}; diff --git a/packages/plugin-birdeye/src/tests/birdeye.test.ts b/packages/plugin-birdeye/src/tests/birdeye.test.ts index 29a95d275a..cb9ac8d9f8 100644 --- a/packages/plugin-birdeye/src/tests/birdeye.test.ts +++ b/packages/plugin-birdeye/src/tests/birdeye.test.ts @@ -1,292 +1,510 @@ import { ICacheManager } from "@elizaos/core"; -import NodeCache from "node-cache"; -import { BirdeyeProvider } from "../birdeye"; - import { afterEach, beforeEach, describe, expect, it, Mock, vi } from "vitest"; +import { BirdeyeProvider } from "../birdeye"; +import { + API_BASE_URL, + BIRDEYE_ENDPOINTS, + DEFAULT_SUPPORTED_SYMBOLS, +} from "../constants"; +import { convertToStringParams } from "../utils"; describe("BirdeyeProvider", () => { - describe("basic fetching", () => { - let cacheManager: ICacheManager; - let provider: BirdeyeProvider; + let cacheManager: ICacheManager; + let provider: BirdeyeProvider; + + beforeEach(() => { + cacheManager = { + get: vi.fn(), + set: vi.fn(), + } as unknown as ICacheManager; + provider = new BirdeyeProvider(cacheManager); + global.fetch = vi.fn(); + + vi.mock("@elizaos/core", () => ({ + settings: { + get: vi.fn().mockImplementation((key) => { + if (key === "BIRDEYE_API_KEY") + return process.env.BIRDEYE_API_KEY || "test-api-key"; + if (key === "BIRDEYE_CHAIN") return "solana"; + return undefined; + }), + }, + ICacheManager: vi.fn(), + })); + }); - beforeEach(() => { - cacheManager = { - get: vi.fn(), - set: vi.fn(), - } as unknown as ICacheManager; - provider = new BirdeyeProvider(cacheManager); - global.fetch = vi.fn(); - }); + afterEach(() => { + vi.clearAllMocks(); + }); - afterEach(() => { - vi.clearAllMocks(); + const mockSuccessResponse = (data: any) => { + (fetch as Mock).mockResolvedValue({ + ok: true, + json: async () => ({ data, success: true }), + }); + }; + + const expectFetchCall = ( + endpoint: string, + params?: any, + method = "GET" + ) => { + const url = `${API_BASE_URL}${endpoint}${ + params && method === "GET" + ? `?${new URLSearchParams(convertToStringParams(params))}` + : "" + }`; + + expect(fetch).toHaveBeenCalledWith(url, expect.anything()); + }; + + describe("Defi Endpoints", () => { + it("should fetch supported networks", async () => { + const mockData = { chains: ["solana", "ethereum"] }; + mockSuccessResponse(mockData); + const result = await provider.fetchDefiSupportedNetworks(); + expect(result.data).toEqual(mockData); + expectFetchCall(BIRDEYE_ENDPOINTS.defi.networks); }); - it("should fetch price by symbol", async () => { - const mockResponse = { price: 100 }; - (fetch as Mock).mockResolvedValue({ - ok: true, - json: async () => mockResponse, + it("should fetch price", async () => { + const mockData = { value: 100 }; + mockSuccessResponse(mockData); + const result = await provider.fetchDefiPrice({ + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + }); + expect(result.data).toEqual(mockData); + expectFetchCall(BIRDEYE_ENDPOINTS.defi.price, { + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, }); - - const result = await provider.fetchPriceBySymbol("SOL"); - - expect(result).toEqual(mockResponse); - expect(fetch).toHaveBeenCalledWith( - "https://public-api.birdeye.so/defi/price?address=So11111111111111111111111111111111111111112", - expect.any(Object) - ); }); - it("should fetch token security by symbol", async () => { - const mockResponse = { security: "secure" }; - (fetch as Mock).mockResolvedValue({ - ok: true, - json: async () => mockResponse, + it("should fetch multiple prices", async () => { + const mockData = { prices: {} }; + mockSuccessResponse(mockData); + const result = await provider.fetchDefiPriceMultiple({ + list_address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + }); + expect(result.data).toEqual(mockData); + expectFetchCall(BIRDEYE_ENDPOINTS.defi.price_multi, { + list_address: DEFAULT_SUPPORTED_SYMBOLS.SOL, }); - - const result = await provider.fetchTokenSecurityBySymbol("SOL"); - - expect(result).toEqual(mockResponse); - expect(fetch).toHaveBeenCalledWith( - "https://public-api.birdeye.so/defi/token_security?address=So11111111111111111111111111111111111111112", - expect.any(Object) - ); }); - it("should fetch token trade data by symbol", async () => { - const mockResponse = { volume: 1000 }; - (fetch as Mock).mockResolvedValue({ - ok: true, - json: async () => mockResponse, + it("should fetch multiple prices via POST", async () => { + const mockData = { prices: {} }; + mockSuccessResponse(mockData); + const result = await provider.fetchDefiPriceMultiple_POST({ + list_address: DEFAULT_SUPPORTED_SYMBOLS.SOL, }); - - const result = await provider.fetchTokenTradeDataBySymbol("SOL"); - - expect(result).toEqual(mockResponse); - expect(fetch).toHaveBeenCalledWith( - "https://public-api.birdeye.so/defi/v3/token/trade-data/single?address=So11111111111111111111111111111111111111112", - expect.any(Object) + expect(result.data).toEqual(mockData); + expectFetchCall( + BIRDEYE_ENDPOINTS.defi.price_multi_POST, + { list_address: DEFAULT_SUPPORTED_SYMBOLS.SOL }, + "POST" ); }); - it("should fetch wallet portfolio", async () => { - const mockResponse = { portfolio: [] }; - (fetch as Mock).mockResolvedValue({ - ok: true, - json: async () => mockResponse, + it("should fetch historical price", async () => { + const mockData = { items: [] }; + mockSuccessResponse(mockData); + const result = await provider.fetchDefiPriceHistorical({ + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + type: "1H", + address_type: "token", + }); + expect(result.data).toEqual(mockData); + expectFetchCall(BIRDEYE_ENDPOINTS.defi.history_price, { + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + type: "1H", + address_type: "token", }); - - const result = await provider.fetchWalletPortfolio( - "some-wallet-address" - ); - - expect(result).toEqual(mockResponse); - expect(fetch).toHaveBeenCalledWith( - "https://public-api.birdeye.so/v1/wallet/token_list?wallet=some-wallet-address", - expect.any(Object) - ); }); }); - describe("retries options", () => { - let cacheManager: ICacheManager; - let provider: BirdeyeProvider; - - beforeEach(() => { - cacheManager = { - get: vi.fn(), - set: vi.fn(), - } as unknown as ICacheManager; - provider = new BirdeyeProvider(cacheManager); - global.fetch = vi.fn(); + describe("Token Endpoints", () => { + it("should fetch token list", async () => { + const mockData = { tokens: [] }; + mockSuccessResponse(mockData); + const result = await provider.fetchTokenList({}); + expect(result.data).toEqual(mockData); + expectFetchCall(BIRDEYE_ENDPOINTS.token.list_all, {}); }); - afterEach(() => { - vi.clearAllMocks(); + it("should fetch token security", async () => { + const mockData = { + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + totalSupply: 1000000, + mintable: false, + proxied: false, + ownerAddress: "owner123", + creatorAddress: "creator123", + securityChecks: { + honeypot: false, + trading_cooldown: false, + transfer_pausable: false, + is_blacklisted: false, + is_whitelisted: false, + is_proxy: false, + is_mintable: false, + can_take_back_ownership: false, + hidden_owner: false, + anti_whale_modifiable: false, + is_anti_whale: false, + trading_pausable: false, + can_be_blacklisted: false, + is_true_token: true, + is_airdrop_scam: false, + slippage_modifiable: false, + is_honeypot: false, + transfer_pausable_time: false, + is_wrapped: false, + }, + }; + mockSuccessResponse(mockData); + const result = await provider.fetchTokenSecurityByAddress({ + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + }); + expect(result.data).toEqual(mockData); + expectFetchCall(BIRDEYE_ENDPOINTS.token.security, { + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + }); }); - it("should retry fetch on failure and succeed", async () => { - const mockResponse = { price: 100 }; - (fetch as Mock) - .mockRejectedValueOnce(new Error("Network error")) // First attempt fails - .mockResolvedValueOnce({ - ok: true, - json: async () => mockResponse, - }); // Second attempt succeeds - - const result = await provider.fetchPriceBySymbol("SOL"); - - expect(result).toEqual(mockResponse); - expect(fetch).toHaveBeenCalledTimes(2); // Ensure it retried + it("should fetch token overview", async () => { + const mockData = { + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + decimals: 9, + symbol: "SOL", + name: "Solana", + extensions: { + coingeckoId: "solana", + website: "https://solana.com", + telegram: "solana", + twitter: "solana", + description: "Solana blockchain token", + }, + logoURI: "https://example.com/sol.png", + liquidity: 1000000, + price: 100, + priceChange24hPercent: 5, + uniqueWallet24h: 1000, + }; + mockSuccessResponse(mockData); + const result = await provider.fetchTokenOverview({ + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + }); + expect(result.data).toEqual(mockData); + expectFetchCall(BIRDEYE_ENDPOINTS.token.overview, { + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + }); }); - it("should fail after max retries", async () => { - const error = new Error("Network error"); - (fetch as Mock) - .mockRejectedValueOnce(error) - .mockRejectedValueOnce(error) // Second attempt fails - .mockRejectedValueOnce(error); // Third attempt also fails - - await expect(provider.fetchPriceBySymbol("SOL")).rejects.toThrow( - "Network error" - ); - - expect(fetch).toHaveBeenCalledTimes(3); // Ensure it retried + it("should fetch token trending", async () => { + const mockData = { + updateUnixTime: 1234567890, + updateTime: "2024-01-01T00:00:00Z", + tokens: [], + total: 0, + }; + mockSuccessResponse(mockData); + const result = await provider.fetchTokenTrending(); + expect(result.data).toEqual(mockData); + expectFetchCall(BIRDEYE_ENDPOINTS.token.trending); }); }); - describe("with custom symbols", () => { - let cacheManager: ICacheManager; - let provider: BirdeyeProvider; - - beforeEach(() => { - cacheManager = { - get: vi.fn(), - set: vi.fn(), - } as unknown as ICacheManager; - provider = new BirdeyeProvider(cacheManager, { - WETH: "0x32323232323232", + describe("Wallet Endpoints", () => { + it("should fetch wallet portfolio", async () => { + const mockData = { + wallet: "test-wallet", + totalUsd: 1000, + items: [ + { + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + name: "Solana", + symbol: "SOL", + decimals: 9, + balance: "1000000000", + uiAmount: 1, + chainId: "solana", + logoURI: "https://example.com/sol.png", + priceUsd: 100, + valueUsd: 100, + }, + ], + }; + mockSuccessResponse(mockData); + const result = await provider.fetchWalletPortfolio({ + wallet: "test-wallet", + }); + expect(result.data).toEqual(mockData); + expectFetchCall(BIRDEYE_ENDPOINTS.wallet.portfolio, { + wallet: "test-wallet", }); - global.fetch = vi.fn(); }); - it("should fetch price for a custom symbol WETH", async () => { - const mockResponse = { price: 2000 }; - (fetch as Mock).mockResolvedValue({ - ok: true, - json: async () => mockResponse, + it("should fetch wallet token balance", async () => { + const mockData = { + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + name: "Solana", + symbol: "SOL", + decimals: 9, + balance: 1000000000, + uiAmount: 1, + chainId: "solana", + priceUsd: 100, + valueUsd: 100, + }; + mockSuccessResponse(mockData); + const result = await provider.fetchWalletTokenBalance({ + wallet: "test-wallet", + token_address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + }); + expect(result.data).toEqual(mockData); + expectFetchCall(BIRDEYE_ENDPOINTS.wallet.token_balance, { + wallet: "test-wallet", + token_address: DEFAULT_SUPPORTED_SYMBOLS.SOL, }); - - const result = await provider.fetchPriceBySymbol("WETH"); - - expect(result).toEqual(mockResponse); - expect(fetch).toHaveBeenCalledWith( - "https://public-api.birdeye.so/defi/price?address=0x32323232323232", - expect.any(Object) - ); }); + }); - it("should fetch token security for a custom symbol WETH", async () => { - const mockResponse = { security: "secure" }; - (fetch as Mock).mockResolvedValue({ - ok: true, - json: async () => mockResponse, + describe("Pair Endpoints", () => { + it("should fetch pair overview", async () => { + const mockData = { + address: "pair-address", + name: "SOL/USDC", + base: { + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + decimals: 9, + icon: "https://example.com/sol.png", + symbol: "SOL", + }, + quote: { + address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + decimals: 6, + icon: "https://example.com/usdc.png", + symbol: "USDC", + }, + created_at: "2024-01-01T00:00:00Z", + source: "Raydium", + liquidity: 1000000, + liquidity_change_percentage_24h: 5, + price: 100, + volume_24h: 1000000, + volume_24h_change_percentage_24h: 10, + trade_24h: 1000, + trade_24h_change_percent: 15, + unique_wallet_24h: 500, + unique_wallet_24h_change_percent: 20, + }; + mockSuccessResponse(mockData); + const result = await provider.fetchPairOverviewSingle({ + address: "pair-address", + }); + expect(result.data).toEqual(mockData); + expectFetchCall(BIRDEYE_ENDPOINTS.pair.overview_single, { + address: "pair-address", }); - - const result = await provider.fetchTokenSecurityBySymbol("WETH"); - - expect(result).toEqual(mockResponse); - expect(fetch).toHaveBeenCalledWith( - "https://public-api.birdeye.so/defi/token_security?address=0x32323232323232", - expect.any(Object) - ); }); - it("should fetch token trade data for a custom symbol WETH", async () => { - const mockResponse = { volume: 500 }; - (fetch as Mock).mockResolvedValue({ - ok: true, - json: async () => mockResponse, + it("should fetch multiple pair overview", async () => { + const mockData = { + "pair-1": { + address: "pair-1", + name: "SOL/USDC", + base: { + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + decimals: 9, + icon: "https://example.com/sol.png", + symbol: "SOL", + }, + quote: { + address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + decimals: 6, + icon: "https://example.com/usdc.png", + symbol: "USDC", + }, + created_at: "2024-01-01T00:00:00Z", + source: "Raydium", + liquidity: 1000000, + liquidity_change_percentage_24h: 5, + price: 100, + volume_24h: 1000000, + volume_24h_change_percentage_24h: 10, + trade_24h: 1000, + trade_24h_change_percent: 15, + unique_wallet_24h: 500, + unique_wallet_24h_change_percent: 20, + }, + "pair-2": { + address: "pair-2", + name: "BTC/USDC", + base: { + address: DEFAULT_SUPPORTED_SYMBOLS.BTC, + decimals: 8, + icon: "https://example.com/btc.png", + symbol: "BTC", + }, + quote: { + address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + decimals: 6, + icon: "https://example.com/usdc.png", + symbol: "USDC", + }, + created_at: "2024-01-01T00:00:00Z", + source: "Raydium", + liquidity: 2000000, + liquidity_change_percentage_24h: 3, + price: 50000, + volume_24h: 2000000, + volume_24h_change_percentage_24h: 8, + trade_24h: 500, + trade_24h_change_percent: 12, + unique_wallet_24h: 300, + unique_wallet_24h_change_percent: 15, + }, + }; + mockSuccessResponse(mockData); + const result = await provider.fetchMultiPairOverview({ + list_address: "pair-1,pair-2", + }); + expect(result.data).toEqual(mockData); + expectFetchCall(BIRDEYE_ENDPOINTS.pair.overview_multi, { + list_address: "pair-1,pair-2", }); - - const result = await provider.fetchTokenTradeDataBySymbol("WETH"); - - expect(result).toEqual(mockResponse); - expect(fetch).toHaveBeenCalledWith( - "https://public-api.birdeye.so/defi/v3/token/trade-data/single?address=0x32323232323232", - expect.any(Object) - ); }); }); - describe("with cache", () => { - let cacheManager: ICacheManager; - let provider: BirdeyeProvider; - let nodeCache: NodeCache; + describe("Search Endpoints", () => { + it("should fetch token market search", async () => { + const mockData = { + items: [ + { + type: "token", + result: [ + { + name: "Solana", + symbol: "SOL", + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + fdv: 1000000000, + market_cap: 500000000, + liquidity: 1000000, + volume_24h_change_percent: 10, + price: 100, + price_change_24h_percent: 5, + buy_24h: 500, + buy_24h_change_percent: 15, + sell_24h: 300, + sell_24h_change_percent: -10, + trade_24h: 800, + trade_24h_change_percent: 8, + unique_wallet_24h: 1000, + unique_view_24h_change_percent: 20, + last_trade_human_time: "2024-01-01T00:00:00Z", + last_trade_unix_time: 1704067200, + creation_time: "2020-01-01T00:00:00Z", + volume_24h_usd: 1000000, + logo_uri: "https://example.com/sol.png", + }, + ], + }, + ], + }; + mockSuccessResponse(mockData); + const result = await provider.fetchSearchTokenMarketData({ + keyword: "test", + }); + expect(result.data).toEqual(mockData); + expectFetchCall(BIRDEYE_ENDPOINTS.search.token_market, { + keyword: "test", + }); + }); + }); + describe("Caching", () => { beforeEach(() => { - nodeCache = new NodeCache(); + // Reset the provider with a fresh cache manager for each test cacheManager = { get: vi.fn(), set: vi.fn(), } as unknown as ICacheManager; - provider = new BirdeyeProvider(cacheManager); - provider["cache"] = nodeCache; // Directly set the node cache - global.fetch = vi.fn(); }); - afterEach(() => { - vi.clearAllMocks(); - nodeCache.flushAll(); - }); - - it("should use memory cache when fetching price by symbol", async () => { - const mockResponse = { price: 100 }; - nodeCache.set( - "price/So11111111111111111111111111111111111111112", - mockResponse - ); // Pre-fill cache + it("should use file system cache when available", async () => { + const mockResponse = { data: { value: 100 }, success: true }; + (cacheManager.get as Mock).mockResolvedValue(mockResponse); - const result = await provider.fetchPriceBySymbol("SOL"); + const result = await provider.fetchDefiPrice({ + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + }); expect(result).toEqual(mockResponse); expect(fetch).not.toHaveBeenCalled(); + expect(cacheManager.get).toHaveBeenCalled(); }); - it("should fetch and cache price by symbol", async () => { - const mockResponse = { price: 100 }; + it("should fetch and cache when cache misses", async () => { + const mockResponse = { data: { value: 100 }, success: true }; + (cacheManager.get as Mock).mockResolvedValue(null); (fetch as Mock).mockResolvedValue({ ok: true, json: async () => mockResponse, }); - // First call - should fetch and cache the result - const result1 = await provider.fetchPriceBySymbol("SOL"); - expect(result1).toEqual(mockResponse); + const result = await provider.fetchDefiPrice({ + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + }); + + expect(result).toEqual(mockResponse); expect(fetch).toHaveBeenCalledTimes(1); + expect(cacheManager.set).toHaveBeenCalled(); + }); + }); + + describe("Error Handling", () => { + it("should retry on failure", async () => { + (fetch as Mock) + .mockRejectedValueOnce(new Error("Network error")) + .mockRejectedValueOnce(new Error("Network error")) + .mockResolvedValueOnce({ + ok: true, + json: async () => ({ data: { value: 100 }, success: true }), + }); + + const result = await provider.fetchDefiPrice({ + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + }); - // Second call - should use cache - const result2 = await provider.fetchPriceBySymbol("SOL"); - expect(result2).toEqual(mockResponse); - expect(fetch).toHaveBeenCalledTimes(1); // No additional fetch call + expect(result).toEqual({ data: { value: 100 }, success: true }); + expect(fetch).toHaveBeenCalledTimes(3); }); - it("should use file system cache when fetching price by symbol", async () => { - const mockResponse = { price: 100 }; - (cacheManager.get as Mock).mockResolvedValue(mockResponse); + it("should throw after max retries", async () => { + (fetch as Mock).mockRejectedValue(new Error("Network error")); - // Memory cache miss, should use file system cache - const result = await provider.fetchPriceBySymbol("SOL"); - expect(result).toEqual(mockResponse); - expect(fetch).not.toHaveBeenCalled(); + await expect( + provider.fetchDefiPrice({ + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + }) + ).rejects.toThrow("Network error"); + expect(fetch).toHaveBeenCalledTimes(3); // Default max retries }); - it("should fetch and cache price by symbol using file system cache", async () => { - const mockResponse = { price: 100 }; + it("should handle non-200 responses", async () => { (fetch as Mock).mockResolvedValue({ - ok: true, - json: async () => mockResponse, + ok: false, + status: 404, + text: async () => "Not found", }); - (cacheManager.get as Mock).mockResolvedValue(null); // File system cache miss - - // First call - should fetch and cache the result - const result1 = await provider.fetchPriceBySymbol("SOL"); - expect(result1).toEqual(mockResponse); - expect(fetch).toHaveBeenCalledTimes(1); - expect(cacheManager.set).toHaveBeenCalledWith( - expect.stringContaining( - "birdeye/data/price/So11111111111111111111111111111111111111112" - ), - mockResponse, - expect.any(Object) - ); - // Second call - should use cache - const result2 = await provider.fetchPriceBySymbol("SOL"); - expect(result2).toEqual(mockResponse); - expect(fetch).toHaveBeenCalledTimes(1); // No additional fetch call + await expect( + provider.fetchDefiPrice({ + address: DEFAULT_SUPPORTED_SYMBOLS.SOL, + }) + ).rejects.toThrow("HTTP error! status: 404, message: Not found"); + expect(fetch).toHaveBeenCalledTimes(3); // Should still retry on HTTP errors }); }); }); diff --git a/packages/plugin-birdeye/src/types/api/common.ts b/packages/plugin-birdeye/src/types/api/common.ts new file mode 100644 index 0000000000..720d9b1f51 --- /dev/null +++ b/packages/plugin-birdeye/src/types/api/common.ts @@ -0,0 +1,293 @@ +import { + BaseQuoteParams, + DefiHistoryPriceParams, + DefiMultiPriceParams, + DefiPriceParams, + HistoricalPriceUnixParams, + MultiPriceVolumeParams, + OHLCVParams, + PriceVolumeParams, +} from "./defi"; +import { + OHLCVPairParams, + PairOverviewMultiParams, + PairOverviewSingleParams, + PairTradesParams, +} from "./pair"; +import { TokenMarketSearchParams } from "./search"; +import { + AllMarketsParams, + MintBurnParams, + NewListingParams, + TokenCreationInfoParams, + TokenHoldersParams, + TokenListV2Params, + TokenMarketDataParams, + TokenMetadataMultiParams, + TokenMetadataSingleParams, + TokenOverviewParams, + TokenSecurityParams, + TokenTradeDataMultiParams, + TokenTradeDataSingleParams, + TokenTradesParams, + TopTradersParams, +} from "./token"; +import { GainersLosersParams, TraderTransactionsSeekParams } from "./trader"; +import { + WalletPortfolioMultichainParams, + WalletPortfolioParams, + WalletSimulationParams, + WalletTokenBalanceParams, + WalletTransactionHistoryMultichainParams, + WalletTransactionHistoryParams, +} from "./wallet"; + +export type BirdeyeApiParams = + | DefiPriceParams + | DefiMultiPriceParams + | DefiHistoryPriceParams + | HistoricalPriceUnixParams + | OHLCVParams + | PriceVolumeParams + | MultiPriceVolumeParams + | PairTradesParams + | OHLCVPairParams + | PairOverviewMultiParams + | PairOverviewSingleParams + | TokenMarketSearchParams + | TokenTradesParams + | TokenSecurityParams + | TokenOverviewParams + | TokenCreationInfoParams + | TokenListV2Params + | TokenMetadataMultiParams + | TokenTradeDataMultiParams + | GainersLosersParams + | TraderTransactionsSeekParams + | WalletPortfolioParams + | WalletTokenBalanceParams + | WalletTransactionHistoryParams + | BaseQuoteParams + | TokenHoldersParams + | MintBurnParams + | TopTradersParams + | AllMarketsParams + | NewListingParams + | TokenMetadataSingleParams + | TokenMarketDataParams + | TokenTradeDataSingleParams + | WalletPortfolioMultichainParams + | WalletTransactionHistoryMultichainParams + | WalletSimulationParams + | Record; + +export interface BirdeyeApiResponseWrapper { + data: T; + success: boolean; +} + +export type BirdeyeApiResponse = BirdeyeApiResponseWrapper; + +export type TimeInterval = + | "1m" + | "3m" + | "5m" + | "15m" + | "30m" + | "1H" + | "2H" + | "4H" + | "6H" + | "8H" + | "12H" + | "1D" + | "3D" + | "1W" + | "1M"; + +export interface TokenTradeData { + address: string; + holder: number; + market: number; + last_trade_unix_time: number; + last_trade_human_time: string; + price: number; + history_30m_price: number; + price_change_30m_percent: number; + history_1h_price: number; + price_change_1h_percent: number; + history_2h_price: number; + price_change_2h_percent: number; + history_4h_price: number; + price_change_4h_percent: number; + history_6h_price: number; + price_change_6h_percent: number; + history_8h_price: number; + price_change_8h_percent: number; + history_12h_price: number; + price_change_12h_percent: number; + history_24h_price: number; + price_change_24h_percent: number; + unique_wallet_30m: number; + unique_wallet_history_30m: number; + unique_wallet_30m_change_percent: number | null; + unique_wallet_1h: number; + unique_wallet_history_1h: number; + unique_wallet_1h_change_percent: number | null; + unique_wallet_2h: number; + unique_wallet_history_2h: number; + unique_wallet_2h_change_percent: number | null; + unique_wallet_4h: number; + unique_wallet_history_4h: number; + unique_wallet_4h_change_percent: number | null; + unique_wallet_8h: number; + unique_wallet_history_8h: number; + unique_wallet_8h_change_percent: number | null; + unique_wallet_24h: number; + unique_wallet_history_24h: number; + unique_wallet_24h_change_percent: number | null; + trade_30m: number; + trade_history_30m: number; + trade_30m_change_percent: number; + sell_30m: number; + sell_history_30m: number; + sell_30m_change_percent: number; + buy_30m: number; + buy_history_30m: number; + buy_30m_change_percent: number; + volume_30m: number; + volume_30m_usd: number; + volume_history_30m: number; + volume_history_30m_usd: number; + volume_30m_change_percent: number; + volume_buy_30m: number; + volume_buy_30m_usd: number; + volume_buy_history_30m: number; + volume_buy_history_30m_usd: number; + volume_buy_30m_change_percent: number; + volume_sell_30m: number; + volume_sell_30m_usd: number; + volume_sell_history_30m: number; + volume_sell_history_30m_usd: number; + volume_sell_30m_change_percent: number; + trade_1h: number; + trade_history_1h: number; + trade_1h_change_percent: number; + sell_1h: number; + sell_history_1h: number; + sell_1h_change_percent: number; + buy_1h: number; + buy_history_1h: number; + buy_1h_change_percent: number; + volume_1h: number; + volume_1h_usd: number; + volume_history_1h: number; + volume_history_1h_usd: number; + volume_1h_change_percent: number; + volume_buy_1h: number; + volume_buy_1h_usd: number; + volume_buy_history_1h: number; + volume_buy_history_1h_usd: number; + volume_buy_1h_change_percent: number; + volume_sell_1h: number; + volume_sell_1h_usd: number; + volume_sell_history_1h: number; + volume_sell_history_1h_usd: number; + volume_sell_1h_change_percent: number; + trade_2h: number; + trade_history_2h: number; + trade_2h_change_percent: number; + sell_2h: number; + sell_history_2h: number; + sell_2h_change_percent: number; + buy_2h: number; + buy_history_2h: number; + buy_2h_change_percent: number; + volume_2h: number; + volume_2h_usd: number; + volume_history_2h: number; + volume_history_2h_usd: number; + volume_2h_change_percent: number; + volume_buy_2h: number; + volume_buy_2h_usd: number; + volume_buy_history_2h: number; + volume_buy_history_2h_usd: number; + volume_buy_2h_change_percent: number; + volume_sell_2h: number; + volume_sell_2h_usd: number; + volume_sell_history_2h: number; + volume_sell_history_2h_usd: number; + volume_sell_2h_change_percent: number; + trade_4h: number; + trade_history_4h: number; + trade_4h_change_percent: number; + sell_4h: number; + sell_history_4h: number; + sell_4h_change_percent: number; + buy_4h: number; + buy_history_4h: number; + buy_4h_change_percent: number; + volume_4h: number; + volume_4h_usd: number; + volume_history_4h: number; + volume_history_4h_usd: number; + volume_4h_change_percent: number; + volume_buy_4h: number; + volume_buy_4h_usd: number; + volume_buy_history_4h: number; + volume_buy_history_4h_usd: number; + volume_buy_4h_change_percent: number; + volume_sell_4h: number; + volume_sell_4h_usd: number; + volume_sell_history_4h: number; + volume_sell_history_4h_usd: number; + volume_sell_4h_change_percent: number; + trade_8h: number; + trade_history_8h: number; + trade_8h_change_percent: number; + sell_8h: number; + sell_history_8h: number; + sell_8h_change_percent: number; + buy_8h: number; + buy_history_8h: number; + buy_8h_change_percent: number; + volume_8h: number; + volume_8h_usd: number; + volume_history_8h: number; + volume_history_8h_usd: number; + volume_8h_change_percent: number; + volume_buy_8h: number; + volume_buy_8h_usd: number; + volume_buy_history_8h: number; + volume_buy_history_8h_usd: number; + volume_buy_8h_change_percent: number; + volume_sell_8h: number; + volume_sell_8h_usd: number; + volume_sell_history_8h: number; + volume_sell_history_8h_usd: number; + volume_sell_8h_change_percent: number; + trade_24h: number; + trade_history_24h: number; + trade_24h_change_percent: number; + sell_24h: number; + sell_history_24h: number; + sell_24h_change_percent: number; + buy_24h: number; + buy_history_24h: number; + buy_24h_change_percent: number; + volume_24h: number; + volume_24h_usd: number; + volume_history_24h: number; + volume_history_24h_usd: number; + volume_24h_change_percent: number; + volume_buy_24h: number; + volume_buy_24h_usd: number; + volume_buy_history_24h: number; + volume_buy_history_24h_usd: number; + volume_buy_24h_change_percent: number; + volume_sell_24h: number; + volume_sell_24h_usd: number; + volume_sell_history_24h: number; + volume_sell_history_24h_usd: number; + volume_sell_24h_change_percent: number; +} diff --git a/packages/plugin-birdeye/src/types/api/defi.ts b/packages/plugin-birdeye/src/types/api/defi.ts new file mode 100644 index 0000000000..c66384a5cd --- /dev/null +++ b/packages/plugin-birdeye/src/types/api/defi.ts @@ -0,0 +1,218 @@ +import { TimeInterval } from "./common"; + +// Network Types +export interface DefiNetworksResponse { + success: boolean; + data: { + chains: string[]; + }; +} + +// Price Types +export interface DefiPriceParams { + address: string; + check_liquidity?: number; + include_liquidity?: boolean; +} + +export interface DefiPriceResponse { + success: boolean; + data: { + value: number; + updateUnixTime?: number; + updateHumanTime?: string; + liquidity?: number; + }; +} + +// Multi Price Types +export interface DefiMultiPriceParams { + list_address: string; + check_liquidity?: number; + include_liquidity?: boolean; +} + +export interface DefiMultiPriceResponse { + success: boolean; + data: { + [address: string]: { + value?: number; + updateUnixTime?: number; + updateHumanTime?: string; + priceChange24h?: number; + }; + }; +} + +// Multi Price Types POST +export interface DefiMultiPriceParamsPOST { + check_liquidity?: number; + include_liquidity?: boolean; + list_address: string; +} + +// History Price Types +export interface DefiHistoryPriceParams { + address: string; + address_type: "token" | "pair"; + type: TimeInterval; + time_from?: number; + time_to?: number; +} + +export interface DefiHistoryPriceResponse { + success: boolean; + data: { + items: Array<{ + unixTime?: number; + value?: number; + }>; + }; +} + +// Historical Price Unix Types +export interface HistoricalPriceUnixParams { + address: string; + unixtime: number; +} + +export interface HistoricalPriceUnixResponse { + success: boolean; + data: { + value?: number; + updateUnixTime?: number; + priceChange24h?: string; + }; +} + +// OHLCV Types +export interface OHLCVParams { + address: string; + type?: TimeInterval; + time_from?: number; + time_to?: number; +} + +export interface OHLCVResponse { + success: boolean; + data: { + items: Array<{ + unixTime?: number; + address?: string; + type?: TimeInterval; + o?: number; + h?: number; + l?: number; + c?: number; + v?: number; + }>; + }; +} + +// Price Volume Types +export interface PriceVolumeParams { + address: string; + type: TimeInterval; +} + +export interface PriceVolumeResponse { + success: boolean; + data: { + price?: number; + updateUnixTime?: number; + updateHumanTime?: string; + volumeUSD?: number; + volumeChangePercent?: number; + priceChangePercent?: number; + }; +} + +// Multi Price Volume Types +export interface MultiPriceVolumeParams { + list_address: string; + type?: TimeInterval; +} + +export interface MultiPriceVolumeResponse { + success: boolean; + data: { + [address: string]: { + price?: number; + updateUnixTime?: number; + updateHumanTime?: string; + volumeUSD?: number; + volumeChangePercent?: number; + priceChangePercent?: number; + }; + }; +} + +// Base Quote Types +export interface BaseQuoteParams { + base_address: string; + quote_address: string; + type?: TimeInterval; + time_from?: number; + time_to?: number; +} + +export interface BaseQuoteResponse { + success: boolean; + data: { + unixTime?: number; + vBase?: number; + vQuote?: number; + o?: number; + h?: number; + l?: number; + c?: number; + }; +} + +// Token Trades Types +export interface DefiTradesTokenParams { + address: string; + limit?: number; + offset?: number; + tx_type?: "swap" | "add" | "remove" | "all"; + before_time?: number; + after_time?: number; +} + +export interface DefiTradesTokenInfo { + symbol: string; + decimals: number; + address: string; + amount: number; + uiAmount: number; + price: number | null; + nearestPrice: number | null; + changeAmount: number; + uiChangeAmount: number; + feeInfo?: any | null; +} + +export interface DefiTradesTokenResponse { + success: boolean; + data: { + items: Array<{ + quote?: DefiTradesTokenInfo; + base?: DefiTradesTokenInfo; + basePrice?: number | null; + quotePrice?: number | null; + txHash?: string; + source?: string; + blockUnixTime?: number; + txType?: string; + owner?: string; + side?: string; + alias?: string | null; + pricePair?: number; + from?: DefiTradesTokenInfo; + to?: DefiTradesTokenInfo; + tokenPrice?: number | null; + poolId?: string; + }>; + hasNext?: boolean; + }; +} diff --git a/packages/plugin-birdeye/src/types/api/pair.ts b/packages/plugin-birdeye/src/types/api/pair.ts new file mode 100644 index 0000000000..5e58cf07f7 --- /dev/null +++ b/packages/plugin-birdeye/src/types/api/pair.ts @@ -0,0 +1,198 @@ +import { TimeInterval } from "./common"; + +// Pair Trades Types +export interface PairTradesParams { + pair: string; + limit?: number; + offset?: number; +} + +export interface PairTradesResponse { + success: boolean; + data: { + items: Array<{ + signature?: string; + blockNumber?: number; + unixTime?: number; + type?: "buy" | "sell"; + tokenAddress?: string; + tokenAmount?: number; + tokenAmountUI?: number; + tokenSymbol?: string; + tokenDecimals?: number; + priceUsd?: number; + volumeUsd?: number; + maker?: string; + taker?: string; + txType?: string; + poolAddress?: string; + poolName?: string; + dex?: string; + }>; + }; +} + +// OHLCV Pair Types +export interface OHLCVPairParams { + address: string; + type?: TimeInterval; + time_from?: number; + time_to?: number; +} + +export interface OHLCVPairResponse { + success: boolean; + data: { + items: Array<{ + unixTime?: number; + address?: string; + type?: TimeInterval; + o?: number; + h?: number; + l?: number; + c?: number; + v?: number; + }>; + }; +} + +// Pair Overview Types +export interface PairOverviewMultiParams { + list_address: string; +} + +export interface PairOverviewSingleParams { + address: string; +} + +interface PairOverviewData { + address: string; + name: string; + base: { + address: string; + decimals: number; + icon: string; + symbol: string; + }; + quote: { + address: string; + decimals: number; + icon: string; + symbol: string; + }; + created_at: string; + source: string; + liquidity: number; + liquidity_change_percentage_24h: number | null; + price: number; + volume_24h: number; + volume_24h_change_percentage_24h: number | null; + trade_24h: number; + trade_24h_change_percent: number; + unique_wallet_24h: number; + unique_wallet_24h_change_percent: number | null; + + // Time-based metrics + trade_30m: number; + trade_1h: number; + trade_2h: number; + trade_4h: number; + trade_8h: number; + trade_12h: number; + + trade_30m_change_percent: number; + trade_1h_change_percent: number; + trade_2h_change_percent: number; + trade_4h_change_percent: number; + trade_8h_change_percent: number; + trade_12h_change_percent: number; + + volume_30m: number; + volume_1h: number; + volume_2h: number; + volume_4h: number; + volume_8h: number; + volume_12h: number; + + volume_30m_quote: number; + volume_1h_quote: number; + volume_2h_quote: number; + volume_4h_quote: number; + volume_8h_quote: number; + volume_12h_quote: number; + + volume_30m_base: number; + volume_1h_base: number; + volume_2h_base: number; + volume_4h_base: number; + volume_8h_base: number; + volume_12h_base: number; +} + +export interface PairOverviewSingleResponse { + success: boolean; + data: { + address?: string; + name?: string; + base?: { + address?: string; + decimals?: number; + icon?: string; + symbol?: string; + }; + quote?: { + address?: string; + decimals?: number; + icon?: string; + symbol?: string; + }; + created_at?: string; + source?: string; + liquidity?: number; + liquidity_change_percentage_24h?: number | null; + price?: number; + volume_24h?: number; + volume_24h_change_percentage_24h?: number | null; + trade_24h?: number; + trade_24h_change_percent?: number; + unique_wallet_24h?: number; + unique_wallet_24h_change_percent?: number | null; + trade_30m?: number; + trade_1h?: number; + trade_2h?: number; + trade_4h?: number; + trade_8h?: number; + trade_12h?: number; + trade_30m_change_percent?: number; + trade_1h_change_percent?: number; + trade_2h_change_percent?: number; + trade_4h_change_percent?: number; + trade_8h_change_percent?: number; + trade_12h_change_percent?: number; + volume_30m?: number; + volume_1h?: number; + volume_2h?: number; + volume_4h?: number; + volume_8h?: number; + volume_12h?: number; + volume_30m_quote?: number; + volume_1h_quote?: number; + volume_2h_quote?: number; + volume_4h_quote?: number; + volume_8h_quote?: number; + volume_12h_quote?: number; + volume_30m_base?: number; + volume_1h_base?: number; + volume_2h_base?: number; + volume_4h_base?: number; + volume_8h_base?: number; + volume_12h_base?: number; + }; +} + +export interface PairOverviewMultiResponse { + success: boolean; + data: { + [pair: string]: PairOverviewData; + }; +} diff --git a/packages/plugin-birdeye/src/types/api/search.ts b/packages/plugin-birdeye/src/types/api/search.ts new file mode 100644 index 0000000000..d4235bd065 --- /dev/null +++ b/packages/plugin-birdeye/src/types/api/search.ts @@ -0,0 +1,84 @@ +import { BirdeyeSupportedChain } from "../shared"; + +// Search Types +export interface TokenMarketSearchParams { + chain?: BirdeyeSupportedChain | "all"; + keyword?: string; + target?: "token" | "market" | "all"; + sort_by?: + | "fdv" + | "marketcap" + | "liquidity" + | "price" + | "price_change_24h_percent" + | "trade_24h" + | "trade_24h_change_percent" + | "buy_24h" + | "buy_24h_change_percent" + | "sell_24h" + | "sell_24h_change_percent" + | "unique_wallet_24h" + | "unique_view_24h_change_percent" + | "last_trade_unix_time" + | "volume_24h_usd" + | "volume_24h_change_percent"; + sort_type?: "asc" | "desc"; + verify_token?: boolean; + markets?: string; + offset?: number; + limit?: number; +} + +export interface TokenMarketSearchResponse { + success: boolean; + data: { + items: Array<{ + type?: "token" | "market"; + result?: Array; + }>; + }; +} + +export interface TokenResult { + name: string; + symbol: string; + address: string; + fdv: number; + market_cap: number; + liquidity: number; + volume_24h_change_percent: number; + price: number; + price_change_24h_percent: number; + buy_24h: number; + buy_24h_change_percent: number; + sell_24h: number; + sell_24h_change_percent: number; + trade_24h: number; + trade_24h_change_percent: number; + unique_wallet_24h: number; + unique_view_24h_change_percent: number; + last_trade_human_time: string; + last_trade_unix_time: number; + creation_time: string; + volume_24h_usd: number; + logo_uri: string; +} + +export interface MarketResult { + name: string; + address: string; + liquidity: number; + source: string; + trade_24h: number; + trade_24h_change_percent: number; + unique_wallet_24h: number; + unique_wallet_24h_change_percent: number; + last_trade_human_time: string; + last_trade_unix_time: number; + base_mint: string; + quote_mint: string; + amount_base: number; + amout_quote: number; // Note: typo in API response + creation_time: string; + volume_24h_usd: number; +} diff --git a/packages/plugin-birdeye/src/types/api/token.ts b/packages/plugin-birdeye/src/types/api/token.ts new file mode 100644 index 0000000000..6eeeab7675 --- /dev/null +++ b/packages/plugin-birdeye/src/types/api/token.ts @@ -0,0 +1,634 @@ +import { TimeInterval, TokenTradeData } from "./common"; + +// Token Trades Types +export interface TokenTradesParams { + address: string; + limit?: number; + offset?: number; + type?: "buy" | "sell" | "all"; +} + +export interface TokenTradesResponse { + success: boolean; + data: { + items: Array<{ + signature?: string; + blockNumber?: number; + unixTime?: number; + type?: "buy" | "sell"; + tokenAddress?: string; + tokenAmount?: number; + tokenAmountUI?: number; + tokenSymbol?: string; + tokenDecimals?: number; + priceUsd?: number; + volumeUsd?: number; + maker?: string; + taker?: string; + txType?: string; + poolAddress?: string; + poolName?: string; + dex?: string; + }>; + }; +} + +export interface TokenListParams { + sort_by?: "mc" | "v24hUSD" | "v24hChangePercent"; + sort_type?: "asc" | "desc"; + offset?: number; + limit?: number; + min_liquidity?: number; +} + +// Token List Types +export interface TokenListResponse { + success: boolean; + data: { + tokens: Array<{ + address?: string; + symbol?: string; + name?: string; + decimals?: number; + logoURI?: string; + coingeckoId?: string; + volume24h?: number; + priceChange24h?: number; + price?: number; + }>; + }; +} + +// Token Security Types +export interface TokenSecurityParams { + address: string; +} + +export interface TokenSecurityResponse { + success: boolean; + data: { + address?: string; + totalSupply?: number; + mintable?: boolean; + proxied?: boolean; + proxy?: string; + ownerAddress?: string; + creatorAddress?: string; + securityChecks?: { + honeypot?: boolean; + trading_cooldown?: boolean; + transfer_pausable?: boolean; + is_blacklisted?: boolean; + is_whitelisted?: boolean; + is_proxy?: boolean; + is_mintable?: boolean; + can_take_back_ownership?: boolean; + hidden_owner?: boolean; + anti_whale_modifiable?: boolean; + is_anti_whale?: boolean; + trading_pausable?: boolean; + can_be_blacklisted?: boolean; + is_true_token?: boolean; + is_airdrop_scam?: boolean; + slippage_modifiable?: boolean; + is_honeypot?: boolean; + transfer_pausable_time?: boolean; + is_wrapped?: boolean; + }; + }; +} + +// Token Overview Types +export interface TokenOverviewParams { + address: string; +} + +export interface TokenOverviewResponse { + success: boolean; + data: { + address?: string; + decimals?: number; + symbol?: string; + name?: string; + extensions?: { + coingeckoId?: string; + serumV3Usdc?: string; + serumV3Usdt?: string; + website?: string; + telegram?: string | null; + twitter?: string; + description?: string; + discord?: string; + medium?: string; + }; + logoURI?: string; + liquidity?: number; + lastTradeUnixTime?: number; + lastTradeHumanTime?: string; + price?: number; + history30mPrice?: number; + priceChange30mPercent?: number; + history1hPrice?: number; + priceChange1hPercent?: number; + history2hPrice?: number; + priceChange2hPercent?: number; + history4hPrice?: number; + priceChange4hPercent?: number; + history6hPrice?: number; + priceChange6hPercent?: number; + history8hPrice?: number; + priceChange8hPercent?: number; + history12hPrice?: number; + priceChange12hPercent?: number; + history24hPrice?: number; + priceChange24hPercent?: number; + uniqueWallet30m?: number; + uniqueWalletHistory30m?: number; + uniqueWallet30mChangePercent?: number; + uniqueWallet1h?: number; + uniqueWalletHistory1h?: number; + uniqueWallet1hChangePercent?: number; + uniqueWallet2h?: number; + uniqueWalletHistory2h?: number; + uniqueWallet2hChangePercent?: number; + uniqueWallet4h?: number; + uniqueWalletHistory4h?: number; + uniqueWallet4hChangePercent?: number; + uniqueWallet8h?: number; + uniqueWalletHistory8h?: number; + uniqueWallet8hChangePercent?: number; + uniqueWallet24h?: number; + uniqueWalletHistory24h?: number; + uniqueWallet24hChangePercent?: number; + supply?: number; + mc?: number; + circulatingSupply?: number; + realMc?: number; + holder?: number; + trade30m?: number; + tradeHistory30m?: number; + trade30mChangePercent?: number; + sell30m?: number; + sellHistory30m?: number; + sell30mChangePercent?: number; + buy30m?: number; + buyHistory30m?: number; + buy30mChangePercent?: number; + v30m?: number; + v30mUSD?: number; + vHistory30m?: number; + vHistory30mUSD?: number; + v30mChangePercent?: number; + vBuy30m?: number; + vBuy30mUSD?: number; + vBuyHistory30m?: number; + vBuyHistory30mUSD?: number; + vBuy30mChangePercent?: number; + vSell30m?: number; + vSell30mUSD?: number; + vSellHistory30m?: number; + vSellHistory30mUSD?: number; + vSell30mChangePercent?: number; + trade1h?: number; + tradeHistory1h?: number; + trade1hChangePercent?: number; + sell1h?: number; + sellHistory1h?: number; + sell1hChangePercent?: number; + buy1h?: number; + buyHistory1h?: number; + buy1hChangePercent?: number; + v1h?: number; + v1hUSD?: number; + vHistory1h?: number; + vHistory1hUSD?: number; + v1hChangePercent?: number; + vBuy1h?: number; + vBuy1hUSD?: number; + vBuyHistory1h?: number; + vBuyHistory1hUSD?: number; + vBuy1hChangePercent?: number; + vSell1h?: number; + vSell1hUSD?: number; + vSellHistory1h?: number; + vSellHistory1hUSD?: number; + vSell1hChangePercent?: number; + trade2h?: number; + tradeHistory2h?: number; + trade2hChangePercent?: number; + sell2h?: number; + sellHistory2h?: number; + sell2hChangePercent?: number; + buy2h?: number; + buyHistory2h?: number; + buy2hChangePercent?: number; + v2h?: number; + v2hUSD?: number; + vHistory2h?: number; + vHistory2hUSD?: number; + v2hChangePercent?: number; + vBuy2h?: number; + vBuy2hUSD?: number; + vBuyHistory2h?: number; + vBuyHistory2hUSD?: number; + vBuy2hChangePercent?: number; + vSell2h?: number; + vSell2hUSD?: number; + vSellHistory2h?: number; + vSellHistory2hUSD?: number; + vSell2hChangePercent?: number; + trade4h?: number; + tradeHistory4h?: number; + trade4hChangePercent?: number; + sell4h?: number; + sellHistory4h?: number; + sell4hChangePercent?: number; + buy4h?: number; + buyHistory4h?: number; + buy4hChangePercent?: number; + v4h?: number; + v4hUSD?: number; + vHistory4h?: number; + vHistory4hUSD?: number; + v4hChangePercent?: number; + vBuy4h?: number; + vBuy4hUSD?: number; + vBuyHistory4h?: number; + vBuyHistory4hUSD?: number; + vBuy4hChangePercent?: number; + vSell4h?: number; + vSell4hUSD?: number; + vSellHistory4h?: number; + vSellHistory4hUSD?: number; + vSell4hChangePercent?: number; + trade8h?: number; + tradeHistory8h?: number; + trade8hChangePercent?: number; + sell8h?: number; + sellHistory8h?: number; + sell8hChangePercent?: number; + buy8h?: number; + buyHistory8h?: number; + buy8hChangePercent?: number; + v8h?: number; + v8hUSD?: number; + vHistory8h?: number; + vHistory8hUSD?: number; + v8hChangePercent?: number; + vBuy8h?: number; + vBuy8hUSD?: number; + vBuyHistory8h?: number; + vBuyHistory8hUSD?: number; + vBuy8hChangePercent?: number; + vSell8h?: number; + vSell8hUSD?: number; + vSellHistory8h?: number; + vSellHistory8hUSD?: number; + vSell8hChangePercent?: number; + trade24h?: number; + tradeHistory24h?: number; + trade24hChangePercent?: number; + sell24h?: number; + sellHistory24h?: number; + sell24hChangePercent?: number; + buy24h?: number; + buyHistory24h?: number; + buy24hChangePercent?: number; + v24h?: number; + v24hUSD?: number; + vHistory24h?: number; + vHistory24hUSD?: number; + v24hChangePercent?: number; + vBuy24h?: number; + vBuy24hUSD?: number; + vBuyHistory24h?: number; + vBuyHistory24hUSD?: number; + vBuy24hChangePercent?: number; + vSell24h?: number; + vSell24hUSD?: number; + vSellHistory24h?: number; + vSellHistory24hUSD?: number; + vSell24hChangePercent?: number; + watch?: null; + numberMarkets?: number; + }; +} + +// Token Creation Info Types +export interface TokenCreationInfoParams { + address: string; +} + +export interface TokenCreationInfoResponse { + success: boolean; + data: { + txHash?: string; + slot?: number; + tokenAddress?: string; + decimals?: number; + owner?: string; + blockUnixTime?: number; + blockHumanTime?: string; + }; +} + +export interface TokenTrendingParams { + sort_by?: "rank" | "volume24hUSD" | "liquidity"; + sort_type?: "asc" | "desc"; + offset?: number; + limit?: number; +} + +// Token Trending Types +export interface TokenTrendingResponse { + success: boolean; + data: { + updateUnixTime?: number; + updateTime?: string; + tokens: Array<{ + address?: string; + symbol?: string; + name?: string; + decimals?: number; + liquidity?: number; + logoURI?: string; + volume24hUSD?: number; + rank?: number; + price?: number; + }>; + total?: number; + }; +} + +// Token List V2 Types +export interface TokenListV2Params { + offset?: number; + limit?: number; + sortBy?: string; + sortOrder?: "asc" | "desc"; +} + +// this endpoint is for enterprise only and the response is not documented +export interface TokenListV2Response { + success: boolean; + data: any; +} + +export interface TokenMetadataMultiParams { + list_addresses: string; +} + +export interface TokenMetadataMultiResponse { + success: boolean; + data: { + [address: string]: { + address?: string; + symbol?: string; + name?: string; + decimals?: number; + extensions?: { + coingecko_id?: string; + website?: string; + twitter?: string; + discord?: string; + medium?: string; + }; + logo_uri?: string; + }; + }; +} + +export interface TokenTradeDataMultiParams { + list_addresses: string; +} + +export interface TokenTradeDataMultiResponse { + success: boolean; + data: { + [address: string]: TokenTradeData; + }; +} + +// Token Metadata Single Types +export interface TokenMetadataSingleParams { + address: string; +} + +export interface TokenMetadataSingleResponse { + success: boolean; + data: { + address?: string; + symbol?: string; + name?: string; + decimals?: number; + extensions?: { + coingecko_id?: string; + website?: string; + twitter?: string; + discord?: string; + medium?: string; + }; + logo_uri?: string; + }; +} + +// Token Market Data Types +export interface TokenMarketDataParams { + address: string; +} + +export interface TokenMarketDataResponse { + success: boolean; + data: { + address?: string; + liquidity?: number; + price?: number; + supply?: number; + marketcap?: number; + circulating_supply?: number; + circulating_marketcap?: number; + }; +} + +// Token Trade Data Single Types +export interface TokenTradeDataSingleParams { + address: string; +} + +export interface TokenTradeDataSingleResponse { + success: boolean; + data: TokenTradeData; +} + +// Token Market Stats Types +export interface TokenMarketStatsResponse { + success: boolean; + data: { + address: string; + liquidity: number; + price: number; + supply: number; + marketcap: number; + circulating_supply: number; + circulating_marketcap: number; + }; +} + +// Token Holders Types +export interface TokenHoldersParams { + address: string; + offset?: number; + limit?: number; +} + +export interface TokenHoldersResponse { + success: boolean; + data: { + items: Array<{ + amount?: string; + decimals?: number; + mint?: string; + owner?: string; + token_account?: string; + ui_amount?: number; + }>; + }; +} + +// Token Mint Burn Types +export interface MintBurnParams { + address: string; + sort_by: "block_time"; + sort_type: "asc" | "desc"; + type: "mint" | "burn" | "all"; + after_time?: number; + before_time?: number; + offset?: number; + limit?: number; +} + +export interface MintBurnResponse { + success: boolean; + data: { + items: Array<{ + amount?: string; + block_human_time?: string; + block_time?: number; + common_type?: "mint" | "burn"; + decimals?: number; + mint?: string; + program_id?: string; + slot?: number; + tx_hash?: string; + ui_amount?: number; + ui_amount_string?: string; + }>; + }; +} + +// New Listing Types +export interface NewListingParams { + time_to: number; + meme_platform_enabled: boolean; + limit?: number; +} + +export interface NewListingResponse { + success: boolean; + data: { + items: Array<{ + address: string; + symbol: string; + name: string; + decimals: number; + source: string; + liquidityAddedAt: string; + logoURI: string | null; + liquidity: number; + }>; + }; +} + +// Top Traders Types +export interface TopTradersParams { + address: string; + time_frame: TimeInterval; + sort_type?: "asc" | "desc"; + sort_by?: "volume" | "trade"; + offset?: number; + limit?: number; +} + +export interface TopTradersResponse { + success: boolean; + data: { + items: Array<{ + trader: string; + volume24h: number; + trades24h: number; + profit24h: number; + }>; + total: number; + }; +} + +// All Markets Types +export interface AllMarketsParams { + address: string; + time_frame: TimeInterval; + sort_type: "asc" | "desc"; + sort_by: "volume24h" | "liquidity"; + offset?: number; + limit?: number; +} + +export interface AllMarketsResponse { + success: boolean; + data: { + items: Array<{ + address: string; + base: { + address: string; + decimals: number; + symbol: string; + icon?: string; + }; + quote: { + address: string; + decimals: number; + symbol: string; + icon?: string; + }; + createdAt: string; + liquidity: number; + name: string; + price: number | null; + source: string; + trade24h: number; + trade24hChangePercent: number; + uniqueWallet24h: number; + uniqueWallet24hChangePercent: number; + volume24h: number; + }>; + total: number; + }; +} + +// Token Volume By Owner Types +export interface TokenVolumeByOwnerResponse { + success: boolean; + data: { + items: Array<{ + tokenAddress: string; + owner: string; + tags: string[]; + type: string; + volume: number; + trade: number; + tradeBuy: number; + tradeSell: number; + volumeBuy: number; + volumeSell: number; + }>; + }; +} diff --git a/packages/plugin-birdeye/src/types/api/trader.ts b/packages/plugin-birdeye/src/types/api/trader.ts new file mode 100644 index 0000000000..c338f2fad4 --- /dev/null +++ b/packages/plugin-birdeye/src/types/api/trader.ts @@ -0,0 +1,75 @@ +// Trader Gainers Losers Types +export interface GainersLosersParams { + type: "yesterday" | "today" | "1W"; + sort_by: "PnL"; + sort_type: "asc" | "desc"; + offset?: number; + limit?: number; +} + +export interface GainersLosersResponse { + success: boolean; + data: { + items: Array<{ + network?: string; + address?: string; + pnl?: number; + trade_count?: number; + volume?: number; + }>; + }; +} + +// Trader Transactions Seek Types +export interface TraderTransactionsSeekParams { + address: string; + offset?: number; + limit?: number; + tx_type?: "swap" | "add" | "remove" | "all"; + before_time?: number; + after_time?: number; +} + +export interface TraderTransactionsSeekResponse { + success: boolean; + data: { + items: Array<{ + quote?: { + symbol?: string; + decimals?: number; + address?: string; + amount?: number; + type?: string; + type_swap?: "from" | "to"; + ui_amount?: number; + price?: number | null; + nearest_price?: number; + change_amount?: number; + ui_change_amount?: number; + }; + base?: { + symbol?: string; + decimals?: number; + address?: string; + amount?: number; + type?: string; + type_swap?: "from" | "to"; + fee_info?: any | null; + ui_amount?: number; + price?: number | null; + nearest_price?: number; + change_amount?: number; + ui_change_amount?: number; + }; + base_price?: number | null; + quote_price?: number | null; + tx_hash?: string; + source?: string; + block_unix_time?: number; + tx_type?: string; + address?: string; + owner?: string; + }>; + hasNext?: boolean; + }; +} diff --git a/packages/plugin-birdeye/src/types/api/wallet.ts b/packages/plugin-birdeye/src/types/api/wallet.ts new file mode 100644 index 0000000000..ff3c06b823 --- /dev/null +++ b/packages/plugin-birdeye/src/types/api/wallet.ts @@ -0,0 +1,180 @@ +// Wallet Portfolio Types +export interface WalletPortfolioParams { + wallet: string; +} + +export interface WalletPortfolioResponse { + success: boolean; + data: { + wallet?: string; + totalUsd?: number; + items: Array<{ + address?: string; + name?: string; + symbol?: string; + decimals?: number; + balance?: string; + uiAmount?: number; + chainId?: string; + logoURI?: string; + priceUsd?: number; + valueUsd?: number; + }>; + }; +} + +// Wallet Token Balance Types +export interface WalletTokenBalanceParams { + wallet: string; + token_address: string; +} + +export interface WalletTokenBalanceResponse { + success: boolean; + data: { + address?: string; + name?: string; + symbol?: string; + decimals?: number; + balance?: number; + uiAmount?: number; + chainId?: string; + priceUsd?: number; + valueUsd?: number; + }; +} + +// Wallet Transaction History Types +export interface WalletTransactionHistoryParams { + wallet: string; + limit?: number; + before?: string; +} + +export interface WalletTransactionHistoryResponse { + success: boolean; + data: { + [chain: string]: Array<{ + txHash?: string; + blockNumber?: number; + blockTime?: string; + status?: boolean; + from?: string; + to?: string; + gasUsed?: number; + gasPrice?: number; + fee?: string; + feeUsd?: number; + value?: string; + contractLabel?: { + address?: string; + name?: string; + metadata?: Record; + }; + mainAction?: string; + balanceChange?: Array<{ + name?: string; + symbol?: string; + logoURI?: string; + address?: string; + amount?: number; + decimals?: number; + }>; + }>; + }; +} + +// Wallet Networks Types +export interface WalletNetworksResponse { + success: boolean; + data: { + chains?: string[]; + }; +} + +// Wallet Portfolio Multichain Types +export interface WalletPortfolioMultichainParams { + wallet: string; +} + +export interface WalletPortfolioMultichainResponse { + success: boolean; + data: { + items: Array<{ + chain?: string; + address?: string; + symbol?: string; + name?: string; + decimals?: number; + price?: number; + priceChange24h?: number; + value?: number; + amount?: number; + }>; + total?: number; + totalValue?: number; + }; +} + +// Wallet Transaction History Multichain Types +export interface WalletTransactionHistoryMultichainParams { + wallet: string; +} + +export interface WalletTransactionHistoryMultichainResponse { + success: boolean; + data: { + [chain: string]: Array<{ + txHash?: string; + blockNumber?: number; + blockTime?: string; + status?: boolean; + from?: string; + to?: string; + gasUsed?: number; + gasPrice?: number; + fee?: string; + feeUsd?: number; + value?: string; + contractLabel?: { + address?: string; + name?: string; + metadata?: Record; + }; + mainAction?: string; + balanceChange?: Array<{ + name?: string; + symbol?: string; + logoURI?: string; + address?: string; + amount?: number; + decimals?: number; + }>; + }>; + }; +} + +// Wallet Transaction Simulation Types +export interface WalletSimulationParams { + from?: string; + to?: string; + data?: string; + value?: string; +} + +export interface WalletSimulationResponse { + success: boolean; + data: { + balanceChange: Array<{ + index?: number; + before?: number; + after?: number; + address?: string; + name?: string; + symbol?: string; + logoURI?: string; + decimals?: number; + }>; + gasUsed?: number; + }; +} diff --git a/packages/plugin-birdeye/src/types/search-token.ts b/packages/plugin-birdeye/src/types/search-token.ts deleted file mode 100644 index 347f5dc10f..0000000000 --- a/packages/plugin-birdeye/src/types/search-token.ts +++ /dev/null @@ -1,43 +0,0 @@ -export interface SearchTokenItem { - address: string; - name: string; - symbol: string; - price: number; - fdv: number; - market_cap: number; - liquidity: number; - volume_24h_usd: number; - volume_24h_change_percent: number; - price_change_24h_percent: number; - network: string; - buy_24h: number; - buy_24h_change_percent: number; - sell_24h: number; - sell_24h_change_percent: number; - trade_24h: number; - trade_24h_change_percent: number; - unique_wallet_24h: number; - unique_view_24h_change_percent: number; - last_trade_human_time: string; - last_trade_unix_time: number; - logo_uri: string; - verified: boolean; -} - -export interface SearchTokenResponse { - data: { - items: Array<{ - type: string; - result: SearchTokenItem[]; - }>; - }; - success: boolean; -} - -export interface SearchTokensOptions { - keyword: string; - chain?: string; - limit?: number; - offset?: number; - type?: "address" | "symbol"; -} diff --git a/packages/plugin-birdeye/src/types/shared.ts b/packages/plugin-birdeye/src/types/shared.ts index 1106716e15..190945b31f 100644 --- a/packages/plugin-birdeye/src/types/shared.ts +++ b/packages/plugin-birdeye/src/types/shared.ts @@ -1,13 +1,13 @@ -import { CHAIN_KEYWORDS } from "../utils"; +import { BIRDEYE_SUPPORTED_CHAINS } from "../utils"; // Types -export type BirdeyeChain = (typeof CHAIN_KEYWORDS)[number]; +export type BirdeyeSupportedChain = (typeof BIRDEYE_SUPPORTED_CHAINS)[number]; export interface BaseAddress { type?: "wallet" | "token" | "contract"; symbol?: string; address: string; - chain: BirdeyeChain; + chain: BirdeyeSupportedChain; } export interface WalletAddress extends BaseAddress { diff --git a/packages/plugin-birdeye/src/types/token-list-v1.ts b/packages/plugin-birdeye/src/types/token-list-v1.ts deleted file mode 100644 index 89d2cacc72..0000000000 --- a/packages/plugin-birdeye/src/types/token-list-v1.ts +++ /dev/null @@ -1,30 +0,0 @@ -export interface TokenListV1Item { - address: string; - name: string; - symbol: string; - decimals: number; - liquidity: number; - mc: number; // market cap - v24hUSD: number; - v24hChangePercent: number; - lastTradeUnixTime: number; - logoURI: string; -} - -export interface TokenListV1Response { - success: boolean; - data: { - updateUnixTime: number; - updateTime: string; - tokens: TokenListV1Item[]; - total: number; - }; -} - -export interface TokenListV1Options { - sort_by?: string; - sort_type?: string; - limit?: number; - offset?: number; - min_liquidity?: number; -} diff --git a/packages/plugin-birdeye/src/types/token-metadata.ts b/packages/plugin-birdeye/src/types/token-metadata.ts deleted file mode 100644 index 541ed20b39..0000000000 --- a/packages/plugin-birdeye/src/types/token-metadata.ts +++ /dev/null @@ -1,19 +0,0 @@ -export interface TokenMetadataResponse { - data: TokenMetadataItem; - success: boolean; -} - -export interface TokenMetadataItem { - address: string; - symbol: string; - name: string; - decimals: number; - extensions: { - coingecko_id?: string; - website?: string; - twitter?: string; - discord?: string; - medium?: string; - }; - logo_uri?: string; -} diff --git a/packages/plugin-birdeye/src/types/wallet.ts b/packages/plugin-birdeye/src/types/wallet.ts deleted file mode 100644 index f90d7c6b64..0000000000 --- a/packages/plugin-birdeye/src/types/wallet.ts +++ /dev/null @@ -1,24 +0,0 @@ -export interface WalletDataResponse { - data: { - items: WalletDataItem[]; - }; - success: boolean; -} - -export interface WalletDataItem { - address: string; - name: string; - symbol: string; - decimals: number; - balance: string; - uiAmount: number; - chainId: string; - logoURI: string; - priceUsd?: number; - valueUsd?: number; -} - -export interface WalletDataOptions { - wallet: string; - chain: string; -} diff --git a/packages/plugin-birdeye/src/utils.ts b/packages/plugin-birdeye/src/utils.ts index b8fe634eb7..4dd4c1d434 100644 --- a/packages/plugin-birdeye/src/utils.ts +++ b/packages/plugin-birdeye/src/utils.ts @@ -1,13 +1,13 @@ import { elizaLogger } from "@elizaos/core"; -import { SearchTokenItem } from "./types/search-token"; -import { BaseAddress, BirdeyeChain } from "./types/shared"; -import { TokenMetadataResponse } from "./types/token-metadata"; -import { WalletDataItem } from "./types/wallet"; +import { BirdeyeApiParams } from "./types/api/common"; +import { TokenMarketSearchResponse, TokenResult } from "./types/api/search"; +import { TokenMetadataSingleResponse } from "./types/api/token"; +import { BaseAddress, BirdeyeSupportedChain } from "./types/shared"; // Constants export const BASE_URL = "https://public-api.birdeye.so"; -export const CHAIN_KEYWORDS = [ +export const BIRDEYE_SUPPORTED_CHAINS = [ "solana", "ethereum", "arbitrum", @@ -23,7 +23,7 @@ export const CHAIN_KEYWORDS = [ ] as const; // Chain abbreviations and alternative names mapping -export const CHAIN_ALIASES: Record = { +export const CHAIN_ALIASES: Record = { // Solana sol: "solana", @@ -107,7 +107,7 @@ export type TimeUnit = keyof typeof TIME_UNITS; export type Timeframe = keyof typeof TIMEFRAME_KEYWORDS; // Helper functions -export const extractChain = (text: string): BirdeyeChain => { +export const extractChain = (text: string): BirdeyeSupportedChain => { // Check for SUI address (0x followed by 64 hex chars) if (text.match(/0x[a-fA-F0-9]{64}/)) { return "sui"; @@ -129,7 +129,7 @@ export const extractAddressesFromString = (text: string): BaseAddress[] => { addresses.push( ...evmAddresses.map((address) => ({ address, - chain: "evm" as BirdeyeChain, // we don't yet know the chain but can assume it's EVM-compatible + chain: "evm" as BirdeyeSupportedChain, // we don't yet know the chain but can assume it's EVM-compatible })) ); } @@ -140,7 +140,7 @@ export const extractAddressesFromString = (text: string): BaseAddress[] => { addresses.push( ...solAddresses.map((address) => ({ address, - chain: "solana" as BirdeyeChain, + chain: "solana" as BirdeyeSupportedChain, })) ); } @@ -151,7 +151,7 @@ export const extractAddressesFromString = (text: string): BaseAddress[] => { addresses.push( ...suiAddresses.map((address) => ({ address, - chain: "sui" as BirdeyeChain, + chain: "sui" as BirdeyeSupportedChain, })) ); } @@ -320,7 +320,7 @@ export async function makeApiRequest( url: string, options: { apiKey: string; - chain?: BirdeyeChain; + chain?: BirdeyeSupportedChain; method?: "GET" | "POST"; body?: any; } @@ -366,8 +366,8 @@ export async function makeApiRequest( // Formatting helpers export const formatTokenInfo = ( - token: SearchTokenItem, - metadata?: TokenMetadataResponse + token: TokenResult, + metadata?: TokenMetadataSingleResponse ): string => { const priceFormatted = token.price != null @@ -396,16 +396,18 @@ export const formatTokenInfo = ( const trades = token.trade_24h != null ? token.trade_24h.toString() : "N/A"; - const age = token.last_trade_unix_time - ? `${Math.floor((Date.now() - new Date(token.last_trade_unix_time).getTime()) / (1000 * 60 * 60 * 24))}d` + const age = token.creation_time + ? `${Math.floor((Date.now() - new Date(token.creation_time).getTime()) / (1000 * 60 * 60 * 24))}d` : "N/A"; let output = `šŸŖ™ ${token.name} @ ${token.symbol}\n` + `šŸ’° USD: $${priceFormatted} (${priceChange})\n` + `šŸ’Ž FDV: ${fdv}\n` + + `šŸ’¦ MCap: ${token.market_cap ? `$${(token.market_cap / 1_000_000).toFixed(2)}M` : "N/A"}\n` + `šŸ’¦ Liq: ${liquidity}\n` + - `šŸ“Š Vol: ${volume} šŸ•°ļø Age: ${age}\n` + + `šŸ“Š Vol: ${volume}\n` + + `šŸ•°ļø Age: ${age}\n` + `šŸ”„ Trades: ${trades}\n` + `šŸ”— Address: ${token.address}`; @@ -439,7 +441,7 @@ export const extractSymbols = ( text: string, // loose mode will try to extract more symbols but may include false positives // strict mode will only extract symbols that are clearly formatted as a symbol using $SOL format - mode: "strict" | "loose" = "strict" + mode: "strict" | "loose" = "loose" ): string[] => { const symbols = new Set(); @@ -477,8 +479,8 @@ export const extractSymbols = ( }; export const formatMetadataResponse = ( - data: TokenMetadataResponse, - chain: BirdeyeChain + data: TokenMetadataSingleResponse, + chain: BirdeyeSupportedChain ): string => { const tokenData = data.data; const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); @@ -534,7 +536,9 @@ export const formatMetadataResponse = ( return response; }; -const formatSocialLinks = (data: TokenMetadataResponse["data"]): string => { +const formatSocialLinks = ( + data: TokenMetadataSingleResponse["data"] +): string => { const links: string[] = []; const { extensions } = data; @@ -566,13 +570,16 @@ const formatSocialLinks = (data: TokenMetadataResponse["data"]): string => { export const waitFor = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); -export const formatPortfolio = (items: WalletDataItem[]) => { +export const formatPortfolio = (data: TokenMarketSearchResponse) => { + const items = data.data?.items.filter( + (item) => item.type === "token" + ) as TokenResult[]; if (!items?.length) return "No tokens found in portfolio"; return items .map((item) => { - const value = item.valueUsd?.toFixed(2); - const amount = item.uiAmount?.toFixed(4); + const value = item?.price?.toFixed(2); + const amount = item?.liquidity?.toFixed(4); return ( `ā€¢ ${item.symbol || "Unknown Token"}: ${amount} tokens` + `${value !== "0.00" ? ` (Value: $${value || "unknown"})` : ""}` @@ -580,3 +587,13 @@ export const formatPortfolio = (items: WalletDataItem[]) => { }) .join("\n"); }; + +export const convertToStringParams = (params: BirdeyeApiParams) => { + return Object.entries(params || {}).reduce( + (acc, [key, value]) => ({ + ...acc, + [key]: value?.toString() || "", + }), + {} as Record + ); +}; From 42c9a372bf03e39cbf1676bcb23546d36f29afe0 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Tue, 31 Dec 2024 13:27:09 -0800 Subject: [PATCH 08/33] chore: major cleanup of services --- .../src/actions/defi/get-ohlcv.ts | 388 ------------------ .../{searchTokens.ts => get-token-info.ts} | 159 ++++--- .../{searchWallets.ts => get-wallet-info.ts} | 10 +- packages/plugin-birdeye/src/actions/report.ts | 179 -------- .../src/actions/test-all-endpoints.ts | 160 +++++--- packages/plugin-birdeye/src/birdeye.ts | 8 +- packages/plugin-birdeye/src/constants.ts | 10 +- packages/plugin-birdeye/src/index.ts | 11 +- .../src/providers/agent-portfolio-provider.ts | 8 +- .../plugin-birdeye/src/types/api/common.ts | 10 +- packages/plugin-birdeye/src/types/api/defi.ts | 5 +- packages/plugin-birdeye/src/types/api/pair.ts | 1 + .../plugin-birdeye/src/types/api/token.ts | 2 +- packages/plugin-birdeye/src/utils.ts | 27 +- 14 files changed, 259 insertions(+), 719 deletions(-) delete mode 100644 packages/plugin-birdeye/src/actions/defi/get-ohlcv.ts rename packages/plugin-birdeye/src/actions/{searchTokens.ts => get-token-info.ts} (63%) rename packages/plugin-birdeye/src/actions/{searchWallets.ts => get-wallet-info.ts} (96%) delete mode 100644 packages/plugin-birdeye/src/actions/report.ts diff --git a/packages/plugin-birdeye/src/actions/defi/get-ohlcv.ts b/packages/plugin-birdeye/src/actions/defi/get-ohlcv.ts deleted file mode 100644 index 6c2b50b8cb..0000000000 --- a/packages/plugin-birdeye/src/actions/defi/get-ohlcv.ts +++ /dev/null @@ -1,388 +0,0 @@ -import { - Action, - ActionExample, - Content, - elizaLogger, - Handler, - HandlerCallback, - IAgentRuntime, - Memory, - State, -} from "@elizaos/core"; -import { getTokenMetadata } from "../../services"; -import { BirdeyeChain } from "../../types/shared"; -import { TokenMetadataResponse } from "../../types/token-metadata"; -import { - BASE_URL, - extractAddressesFromString, - extractChain, - formatTimestamp, - formatValue, - makeApiRequest, -} from "../../utils"; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const exampleResponse = { - success: true, - data: { - items: [ - { - o: 128.27328370924414, - h: 128.6281001340782, - l: 127.91200927364626, - c: 127.97284640184616, - v: 58641.16636665621, - unixTime: 1726670700, - address: "So11111111111111111111111111111111111111112", - type: "15m", - }, - ], - }, -}; - -type OHLCVResponse = typeof exampleResponse; - -type TimeInterval = - | "1m" - | "3m" - | "5m" - | "15m" - | "30m" - | "1H" - | "2H" - | "4H" - | "6H" - | "8H" - | "12H" - | "1D" - | "3D" - | "1W" - | "1M"; - -const TIME_INTERVALS: Record = { - "1m": ["1 minute", "1min", "1m"], - "3m": ["3 minutes", "3min", "3m"], - "5m": ["5 minutes", "5min", "5m"], - "15m": ["15 minutes", "15min", "15m"], - "30m": ["30 minutes", "30min", "30m"], - "1H": ["1 hour", "1hr", "1h"], - "2H": ["2 hours", "2hr", "2h"], - "4H": ["4 hours", "4hr", "4h"], - "6H": ["6 hours", "6hr", "6h"], - "8H": ["8 hours", "8hr", "8h"], - "12H": ["12 hours", "12hr", "12h"], - "1D": ["1 day", "daily", "1d"], - "3D": ["3 days", "3day", "3d"], - "1W": ["1 week", "weekly", "1w"], - "1M": ["1 month", "monthly", "1m"], -}; - -const DEFAULT_INTERVAL: TimeInterval = "1D"; - -// Constants for keyword matching -const OHLCV_KEYWORDS = [ - "ohlc", - "ohlcv", - "candlestick", - "candle", - "chart", - "price history", - "open close high low", - "opening price", - "closing price", - "historical", -] as const; - -// Helper function to check if text contains OHLCV-related keywords -const containsOHLCVKeyword = (text: string): boolean => { - return OHLCV_KEYWORDS.some((keyword) => - text.toLowerCase().includes(keyword.toLowerCase()) - ); -}; - -const extractTimeInterval = (text: string): TimeInterval => { - const lowerText = text.toLowerCase(); - - // First try exact matches - for (const [interval, keywords] of Object.entries(TIME_INTERVALS)) { - if (keywords.some((keyword) => lowerText.includes(keyword))) { - return interval as TimeInterval; - } - } - - // Then try common variations - if (lowerText.includes("hourly")) return "1H"; - if (lowerText.includes("daily")) return "1D"; - if (lowerText.includes("weekly")) return "1W"; - if (lowerText.includes("monthly")) return "1M"; - - return DEFAULT_INTERVAL; -}; - -const formatVolume = (volume: number): string => { - if (volume >= 1_000_000_000) { - return `$${(volume / 1_000_000_000).toFixed(2)}B`; - } - if (volume >= 1_000_000) { - return `$${(volume / 1_000_000).toFixed(2)}M`; - } - if (volume >= 1_000) { - return `$${(volume / 1_000).toFixed(2)}K`; - } - return `$${volume.toFixed(2)}`; -}; - -const getOHLCVData = async ( - apiKey: string, - contractAddress: string, - chain: BirdeyeChain, - interval: TimeInterval = DEFAULT_INTERVAL -): Promise => { - try { - const params = new URLSearchParams({ - address: contractAddress, - interval: interval.toLowerCase(), - limit: "24", // Get last 24 periods - }); - const url = `${BASE_URL}/defi/ohlcv?${params.toString()}`; - - elizaLogger.info( - `Fetching OHLCV data for ${contractAddress} on ${chain} with interval ${interval} from:`, - url - ); - - return await makeApiRequest(url, { apiKey, chain }); - } catch (error) { - if (error instanceof Error) { - elizaLogger.error("Error fetching OHLCV data:", error.message); - } - return null; - } -}; - -const formatOHLCVResponse = ( - data: OHLCVResponse, - tokenMetadata: TokenMetadataResponse | null, - chain: BirdeyeChain, - interval: TimeInterval -): string => { - const chainName = chain.charAt(0).toUpperCase() + chain.slice(1); - - let tokenInfo = "Unknown Token"; - let tokenLinks = ""; - - if (tokenMetadata?.success) { - const { name, symbol, extensions } = tokenMetadata.data; - tokenInfo = `${name} (${symbol})`; - - const links: string[] = []; - if (extensions.website) links.push(`[Website](${extensions.website})`); - if (extensions.coingecko_id) - links.push( - `[CoinGecko](https://www.coingecko.com/en/coins/${extensions.coingecko_id})` - ); - if (links.length > 0) { - tokenLinks = `\nšŸ“Œ More Information: ${links.join(" ā€¢ ")}`; - } - } - - let response = `OHLCV Data for ${tokenInfo} on ${chainName}${tokenLinks}\n`; - response += `Interval: ${TIME_INTERVALS[interval][0]}\n\n`; - - if (!data.success || !data.data.items || data.data.items.length === 0) { - return response + "No OHLCV data available for the specified period."; - } - - const candles = data.data.items; - const latestCandle = candles[candles.length - 1]; - - // Latest candle information - response += `šŸ“Š Latest Candle (${formatTimestamp(latestCandle.unixTime)})\n`; - response += `ā€¢ Open: ${formatValue(latestCandle.o)}\n`; - response += `ā€¢ High: ${formatValue(latestCandle.h)}\n`; - response += `ā€¢ Low: ${formatValue(latestCandle.l)}\n`; - response += `ā€¢ Close: ${formatValue(latestCandle.c)}\n`; - response += `ā€¢ Volume: ${formatVolume(latestCandle.v)}\n\n`; - - // Price change statistics - const priceChange = latestCandle.c - latestCandle.o; - const priceChangePercent = (priceChange / latestCandle.o) * 100; - const trend = priceChange >= 0 ? "šŸ“ˆ" : "šŸ“‰"; - - response += `${trend} Period Change\n`; - response += `ā€¢ Price Change: ${formatValue(priceChange)} (${priceChangePercent.toFixed(2)}%)\n\n`; - - // Volume analysis - const totalVolume = candles.reduce((sum, candle) => sum + candle.v, 0); - const avgVolume = totalVolume / candles.length; - const highestVolume = Math.max(...candles.map((c) => c.v)); - const lowestVolume = Math.min(...candles.map((c) => c.v)); - - response += `šŸ“Š Volume Analysis\n`; - response += `ā€¢ Total Volume: ${formatVolume(totalVolume)}\n`; - response += `ā€¢ Average Volume: ${formatVolume(avgVolume)}\n`; - response += `ā€¢ Highest Volume: ${formatVolume(highestVolume)}\n`; - response += `ā€¢ Lowest Volume: ${formatVolume(lowestVolume)}\n\n`; - - // Market analysis - const volatility = - ((Math.max(...candles.map((c) => c.h)) - - Math.min(...candles.map((c) => c.l))) / - avgVolume) * - 100; - const volumeLevel = - latestCandle.v > avgVolume * 1.5 - ? "high" - : latestCandle.v > avgVolume - ? "moderate" - : "low"; - const volatilityLevel = - volatility > 5 ? "high" : volatility > 2 ? "moderate" : "low"; - - response += `šŸ“ˆ Market Analysis\n`; - response += `ā€¢ Current volume is ${volumeLevel}\n`; - response += `ā€¢ Market volatility is ${volatilityLevel}\n`; - response += `ā€¢ Overall trend is ${priceChange >= 0 ? "upward" : "downward"} for this period\n`; - - return response; -}; - -export const getOHLCVAction: Action = { - name: "GET_OHLCV", - similes: [ - "SHOW_OHLCV", - "VIEW_CANDLESTICK", - "CHECK_PRICE_CHART", - "DISPLAY_OHLCV", - "GET_CANDLESTICK", - "SHOW_PRICE_CHART", - "VIEW_PRICE_HISTORY", - "CHECK_HISTORICAL_PRICES", - "PRICE_CANDLES", - "MARKET_CANDLES", - ], - description: - "Retrieve and analyze OHLCV (Open, High, Low, Close, Volume) data for a token, including price movements and volume analysis.", - validate: async ( - _runtime: IAgentRuntime, - message: Memory, - _state: State | undefined - ): Promise => { - return containsOHLCVKeyword(message.content.text); - }, - handler: (async ( - runtime: IAgentRuntime, - message: Memory, - _state: State | undefined, - _options: any, - callback: HandlerCallback - ): Promise => { - const callbackData: Content = { - text: "", - action: "GET_OHLCV_RESPONSE", - source: message.content.source, - }; - - const apiKey = runtime.getSetting("BIRDEYE_API_KEY"); - if (!apiKey) { - elizaLogger.error("BIRDEYE_API_KEY not found in runtime settings"); - callbackData.text = - "I'm unable to fetch the OHLCV data due to missing API credentials."; - await callback(callbackData); - return callbackData; - } - - const messageText = message.content.text; - const addresses = extractAddressesFromString(messageText); - if (addresses.length === 0) { - callbackData.text = - "I couldn't find a valid token address in your message."; - await callback(callbackData); - return callbackData; - } - - const chain = extractChain(messageText); - const interval = extractTimeInterval(messageText); - - // First fetch token metadata - const tokenMetadata = await getTokenMetadata( - apiKey, - addresses[0].toString(), - chain as BirdeyeChain - ); - - elizaLogger.info( - `OHLCV action activated for ${addresses[0]} on ${chain} with ${interval} interval` - ); - - const ohlcvData = await getOHLCVData( - apiKey, - addresses[0].toString(), - chain as BirdeyeChain, - interval - ); - - if (!ohlcvData) { - callbackData.text = - "I apologize, but I couldn't retrieve the OHLCV data at the moment."; - await callback(callbackData); - return callbackData; - } - - callbackData.text = formatOHLCVResponse( - ohlcvData, - tokenMetadata, - chain as BirdeyeChain, - interval - ); - await callback(callbackData); - return callbackData; - }) as Handler, - examples: [ - [ - { - user: "{{user1}}", - content: { - text: "Show me the daily OHLCV data for token 0x1234... on Ethereum", - }, - }, - { - user: "{{user2}}", - content: { - text: "Here's the detailed OHLCV analysis including price movements, volume statistics, and market insights.", - action: "GET_OHLCV", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { - text: "What's the hourly candlestick data for ABC123... on Solana?", - }, - }, - { - user: "{{user2}}", - content: { - text: "I'll analyze the hourly OHLCV data and provide you with a comprehensive market overview.", - action: "GET_OHLCV", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { - text: "Get me 15-minute candles for token XYZ... on BSC", - }, - }, - { - user: "{{user2}}", - content: { - text: "I'll fetch the 15-minute OHLCV data and provide detailed market analysis.", - action: "GET_OHLCV", - }, - }, - ], - ] as ActionExample[][], -}; diff --git a/packages/plugin-birdeye/src/actions/searchTokens.ts b/packages/plugin-birdeye/src/actions/get-token-info.ts similarity index 63% rename from packages/plugin-birdeye/src/actions/searchTokens.ts rename to packages/plugin-birdeye/src/actions/get-token-info.ts index dfcb0de4eb..24d57f0e08 100644 --- a/packages/plugin-birdeye/src/actions/searchTokens.ts +++ b/packages/plugin-birdeye/src/actions/get-token-info.ts @@ -1,57 +1,18 @@ import { Action, ActionExample, - composeContext, elizaLogger, - generateText, - ModelClass, type IAgentRuntime, type Memory, type State, } from "@elizaos/core"; import { BirdeyeProvider } from "../birdeye"; -import { extractChain, extractSymbols } from "../utils"; - -const extractTokenSymbolsTemplate = `Given the recent message below: -{{recentMessages}} -Extract all token symbols mentioned in the message. Look for: -- Symbols prefixed with $ (e.g. $SOL, $ETH) -- Well-known symbols in any case (e.g. BTC, eth, SOL) -- Other symbols that are in all caps (e.g. SOL, SUI, NUER) -- Quoted symbols - -When symbols are in lowercase (btc, eth, sol), convert to well-known format (BTC, ETH, SOL). -For mixed case symbols (SOl, eTH), include them as-is unless quoted. - -Respond with a JSON array containing only the extracted symbols, no extra description needed. -Example: -Message: "Check $SOL and btc prices" -Response: ["SOL", "BTC"]`; - -const extractTokenSymbolsFromMessage = async ( - runtime: IAgentRuntime, - message: Memory, - state: State -) => { - const context = composeContext({ - state, - template: extractTokenSymbolsTemplate, - }); - - const response = await generateText({ - runtime, - context, - modelClass: ModelClass.LARGE, - }); - - try { - const regex = new RegExp(/\[(.+)\]/gms); - const normalized = response && regex.exec(response)?.[0]; - return normalized ? JSON.parse(normalized) : []; - } catch { - return []; - } -}; +import { TokenResult } from "../types/api/search"; +import { + extractChain, + extractSymbols, + getTokenResultFromSearchResponse, +} from "../utils"; const formatTokenReport = (token, metadata, security, volume) => { let output = `*Token Security and Trade Report*\n`; @@ -89,8 +50,8 @@ const formatTokenReport = (token, metadata, security, volume) => { return output; }; -export const searchTokensBySymbolAction = { - name: "SEARCH_TOKENS_BY_SYMBOL", +export const getTokenInfoAction = { + name: "GET_TOKEN_INFO", similes: [ "FIND_TOKENS", "TOKEN_SEARCH", @@ -98,6 +59,23 @@ export const searchTokensBySymbolAction = { "CHECK_TOKEN", "REVIEW_TOKEN", "TOKEN_DETAILS", + "GET_TOKEN_INFO", + "TOKEN_INFO", + "TOKEN_REPORT", + "TOKEN_ANALYSIS", + "TOKEN_OVERVIEW", + "TOKEN_SUMMARY", + "TOKEN_INSIGHT", + "TOKEN_DATA", + "TOKEN_STATS", + "TOKEN_METRICS", + "TOKEN_PROFILE", + "TOKEN_REVIEW", + "TOKEN_CHECK", + "TOKEN_LOOKUP", + "TOKEN_FIND", + "TOKEN_DISCOVER", + "TOKEN_EXPLORE", ], description: "Search for detailed token information including security and trade data by symbol", @@ -111,11 +89,7 @@ export const searchTokensBySymbolAction = { try { const provider = new BirdeyeProvider(runtime.cacheManager); - const symbols = await extractTokenSymbolsFromMessage( - runtime, - message, - state - ); + const symbols = extractSymbols(message.content.text, "loose"); if (symbols.length === 0) { callback?.({ text: "No token symbols found in the message" }); @@ -127,29 +101,30 @@ export const searchTokensBySymbolAction = { ); const searchTokenResponses = symbols.map((symbol) => - provider.fetchSearchTokens({ + provider.fetchSearchTokenMarketData({ keyword: symbol, limit: 1, }) ); const results = await Promise.all(searchTokenResponses); - const validResults = results.map( - (r, index) => - r.data.items.find( - (item) => - item.type === "token" && - item.result[0].symbol.toLowerCase() === - symbols[index].toLowerCase() - )?.result[0] + + // get only the token results where the symbol matches + const validResults = results.map((r, index) => + getTokenResultFromSearchResponse(r, symbols[index]) + ); + + // filter out undefined results + const filteredResults = validResults.filter( + (result): result is TokenResult => result !== undefined ); - if (validResults.length === 0) { + if (filteredResults.length === 0) { callback?.({ text: "No matching tokens found" }); return true; } - const resultsWithChains = validResults.map((result) => ({ + const resultsWithChains = filteredResults.map((result) => ({ symbol: result.symbol, address: result.address, chain: extractChain(result.address), @@ -157,11 +132,26 @@ export const searchTokensBySymbolAction = { // Fetch all data in parallel for each token const tokenData = await Promise.all( - resultsWithChains.map(async ({ address, chain, symbol }) => { + resultsWithChains.map(async ({ address, chain }) => { const [metadata, security, volume] = await Promise.all([ - provider.fetchTokenMetadata(address, chain), - provider.fetchTokenSecurityBySymbol(symbol), - provider.fetchTokenTradeDataBySymbol(symbol), + provider.fetchTokenMarketData({ + address, + headers: { + chain, + }, + }), + provider.fetchTokenSecurityByAddress({ + address, + headers: { + chain, + }, + }), + provider.fetchTokenTradeDataSingle({ + address, + headers: { + chain, + }, + }), ]); return { metadata, security, volume }; }) @@ -221,6 +211,41 @@ export const searchTokensBySymbolAction = { action: "CHECK_TOKEN", }, }, + { + user: "user", + content: { + text: "Give me details on $ADA", + action: "TOKEN_DETAILS", + }, + }, + { + user: "user", + content: { + text: "What can you tell me about $DOGE?", + action: "TOKEN_INFO", + }, + }, + { + user: "user", + content: { + text: "I need a report on $XRP", + action: "TOKEN_REPORT", + }, + }, + { + user: "user", + content: { + text: "Analyze $BNB for me", + action: "TOKEN_ANALYSIS", + }, + }, + { + user: "user", + content: { + text: "Overview of $LTC", + action: "TOKEN_OVERVIEW", + }, + }, ], ] as ActionExample[][], } as Action; diff --git a/packages/plugin-birdeye/src/actions/searchWallets.ts b/packages/plugin-birdeye/src/actions/get-wallet-info.ts similarity index 96% rename from packages/plugin-birdeye/src/actions/searchWallets.ts rename to packages/plugin-birdeye/src/actions/get-wallet-info.ts index 6118973973..0586b5121a 100644 --- a/packages/plugin-birdeye/src/actions/searchWallets.ts +++ b/packages/plugin-birdeye/src/actions/get-wallet-info.ts @@ -53,8 +53,8 @@ const extractWalletAddressesFromMessage = async ( } }; -export const searchWalletsAction = { - name: "SEARCH_WALLETS", +export const getWalletInfoAction = { + name: "GET_WALLET_INFO", similes: ["CHECK_WALLET", "WALLET_HOLDINGS", "PORTFOLIO_CHECK"], description: "Check wallet portfolio and holdings information", handler: async ( @@ -87,9 +87,11 @@ export const searchWalletsAction = { // Search Birdeye services for wallet portfolio data const searchAddressesForTokenMatch = addresses.map((address) => - provider.fetchSearchWallets({ + provider.fetchWalletPortfolio({ wallet: address.address, - chain: address.chain, + headers: { + chain: address.chain, + }, }) ); diff --git a/packages/plugin-birdeye/src/actions/report.ts b/packages/plugin-birdeye/src/actions/report.ts deleted file mode 100644 index 331a3e68af..0000000000 --- a/packages/plugin-birdeye/src/actions/report.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { - Action, - ActionExample, - composeContext, - elizaLogger, - generateText, - ModelClass, - type IAgentRuntime, - type Memory, - type State, -} from "@elizaos/core"; -import { BirdeyeProvider } from "../birdeye"; - -const extractTokenSymbolTemplate = `Given the recent message below: -{{recentMessages}} -Extract the 1 latest information about the requested token report: -- Input token symbol -- Extra about this symbol -When the symbol is specified in all lowered case, such as btc, eth, sol..., we should convert it into wellknown symbol. -E.g. btc instead of BTC, sol instead of SOL. -But when we see them in mixed form, such as SOl, DOl, eTH, except the case they're quoted (e.g. 'wEth', 'SOl',...) -When in doubt, specify the concern in the message field, include your suggested value with it. -Respond exactly a JSON object containing only the extracted values, no extra description or message needed. -Use null for any values that cannot be determined. The result should be a valid JSON object with the following schema: -{ - "symbol": string | null, - "message": string | null, -} -Examples: - Message: 'Tell me about BTC' - Response: '{ "symbol": "BTC", "message": null}' - Message: 'Do you know about SOl.' - Response: '{ "symbol": "SOl", "message": "We've found SOL seems match, is that what you want?"}' -`; - -const formatTokenReport = (data) => { - let output = `*Token Security and Trade Report*\n`; - output += `Token symbol: ${data.symbol}\n`; - output += `Token Address: ${data.tokenAddress}\n\n`; - - output += `*Ownership Distribution:*\n`; - output += `- Owner Balance: ${data.security.ownerBalance}\n`; - output += `- Creator Balance: ${data.security.creatorBalance}\n`; - output += `- Owner Percentage: ${data.security.ownerPercentage}%\n`; - output += `- Creator Percentage: ${data.security.creatorPercentage}%\n`; - output += `- Top 10 Holders Balance: ${data.security.top10HolderBalance}\n`; - output += `- Top 10 Holders Percentage: ${data.security.top10HolderPercent}%\n\n`; - - // Trade Data - output += `*Trade Data:*\n`; - output += `- Holders: ${data.volume.holder}\n`; - output += `- Unique Wallets (24h): ${data.volume.unique_wallet_24h}\n`; - output += `- Price Change (24h): ${data.volume.price_change_24h_percent}%\n`; - output += `- Price Change (12h): ${data.volume.price_change_12h_percent}%\n`; - output += `- Volume (24h USD): $${data.volume.volume_24h_usd}\n`; - output += `- Current Price: $${data.volume.price}\n\n`; - - return output; -}; - -const extractTokenSymbol = async ( - runtime: IAgentRuntime, - message: Memory, - state: State, - options: any, - callback?: any -) => { - const context = composeContext({ - state, - template: extractTokenSymbolTemplate, - }); - - const response = await generateText({ - runtime, - context, - modelClass: ModelClass.LARGE, - }); - - elizaLogger.log("Response", response); - - try { - const regex = new RegExp(/\{(.+)\}/gms); - const normalized = response && regex.exec(response)?.[0]; - elizaLogger.debug("Normalized data", normalized); - return normalized && JSON.parse(normalized); - } catch { - callback?.({ text: response }); - return true; - } -}; - -export const reportToken = { - name: "REPORT_TOKEN", - similes: ["CHECK_TOKEN", "REVIEW_TOKEN", "TOKEN_DETAILS"], - description: "Check background data for a given token", - handler: async ( - runtime: IAgentRuntime, - message: Memory, - state: State, - options: any, - callback?: any - ) => { - try { - const params = await extractTokenSymbol( - runtime, - message, - state, - options, - callback - ); - - elizaLogger.debug("Params", params); - - if (!params?.symbol) { - callback?.({ text: "I need a token symbol to begin" }); - return true; - } - - if (params?.message) { - // show concern message - callback?.({ text: `*Warning*: ${params.message}` }); - } - - const symbol = params?.symbol; - elizaLogger.log("Fetching birdeye data", symbol); - const provider = new BirdeyeProvider(runtime.cacheManager); - - const [tokenAddress, security, volume] = await Promise.all([ - provider.getTokenAddress(symbol), - provider.fetchTokenSecurityBySymbol(symbol), - provider.fetchTokenTradeDataBySymbol(symbol), - ]); - - elizaLogger.log("Fetching birdeye done"); - const msg = formatTokenReport({ - symbol, - tokenAddress, - security: security.data, - volume: volume.data, - }); - callback?.({ text: msg }); - return true; - } catch (error) { - console.error("Error in reportToken handler:", error.message); - callback?.({ text: `Error: ${error.message}` }); - return false; - } - }, - validate: async (_runtime: IAgentRuntime, _message: Memory) => { - // todo: validate the token symbol - // for example, this action should not be triggered when the message is a wallet address - return true; - }, - examples: [ - [ - { - user: "user", - content: { - text: "Tell me what you know about SOL", - action: "CHECK_TOKEN", - }, - }, - { - user: "user", - content: { - text: "Do you know about SOL", - action: "TOKEN_DETAILS", - }, - }, - { - user: "user", - content: { - text: "Tell me about WETH", - action: "REVIEW_TOKEN", - }, - }, - ], - ] as ActionExample[][], -} as Action; diff --git a/packages/plugin-birdeye/src/actions/test-all-endpoints.ts b/packages/plugin-birdeye/src/actions/test-all-endpoints.ts index b8dbb9ef57..f2c1dcb485 100644 --- a/packages/plugin-birdeye/src/actions/test-all-endpoints.ts +++ b/packages/plugin-birdeye/src/actions/test-all-endpoints.ts @@ -36,39 +36,47 @@ export const testAllEndpointsAction = { list_address: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm", address_type: "token", type: "1D", + tx_type: "all", + sort_type: "desc", unixtime: 1234567890, base_address: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm", quote_address: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm", time_to: 1672531199, // Unix timestamp meme_platform_enabled: true, time_frame: "1D", - sort_type: undefined, sort_by: undefined, list_addresses: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm", wallet: "MfDuWeqSHEqTFVYZ7LoexgAK9dxk7cy4DFJWjWMGVWa", token_address: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm", pair: "samplePair", + before_time: 1672531199, + after_time: 1672331199, }; // Test each fetch function + elizaLogger.info("fetchDefiSupportedNetworks"); await birdeyeProvider.fetchDefiSupportedNetworks(); elizaLogger.success("fetchDefiSupportedNetworks: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchDefiPrice"); await birdeyeProvider.fetchDefiPrice({ ...sampleParams }); elizaLogger.success("fetchDefiPrice: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchDefiPriceMultiple"); await birdeyeProvider.fetchDefiPriceMultiple({ ...sampleParams }); elizaLogger.success("fetchDefiPriceMultiple: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchDefiPriceMultiple_POST"); await birdeyeProvider.fetchDefiPriceMultiple_POST({ ...sampleParams, }); elizaLogger.success("fetchDefiPriceMultiple_POST: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchDefiPriceHistorical"); await birdeyeProvider.fetchDefiPriceHistorical({ ...sampleParams, address_type: "token", @@ -77,32 +85,44 @@ export const testAllEndpointsAction = { elizaLogger.success("fetchDefiPriceHistorical: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchDefiPriceHistoricalByUnixTime"); await birdeyeProvider.fetchDefiPriceHistoricalByUnixTime({ - ...sampleParams, + address: sampleParams.token, }); elizaLogger.success("fetchDefiPriceHistoricalByUnixTime: SUCCESS!"); await waitFor(500); - await birdeyeProvider.fetchDefiTradesToken({ ...sampleParams }); + elizaLogger.info("fetchDefiTradesToken"); + await birdeyeProvider.fetchDefiTradesToken({ + address: sampleParams.token, + }); elizaLogger.success("fetchDefiTradesToken: SUCCESS!"); await waitFor(500); - await birdeyeProvider.fetchDefiTradesPair({ ...sampleParams }); + elizaLogger.info("fetchDefiTradesPair"); + await birdeyeProvider.fetchDefiTradesPair({ + address: sampleParams.token, + }); elizaLogger.success("fetchDefiTradesPair: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchDefiTradesTokenSeekByTime"); await birdeyeProvider.fetchDefiTradesTokenSeekByTime({ - ...sampleParams, + address: sampleParams.token, + before_time: sampleParams.before_time, }); elizaLogger.success("fetchDefiTradesTokenSeekByTime: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchDefiTradesPairSeekByTime"); await birdeyeProvider.fetchDefiTradesPairSeekByTime({ - ...sampleParams, + address: sampleParams.token, + after_time: sampleParams.after_time, }); elizaLogger.success("fetchDefiTradesPairSeekByTime: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchDefiOHLCV"); await birdeyeProvider.fetchDefiOHLCV({ ...sampleParams, type: "1D", @@ -110,6 +130,7 @@ export const testAllEndpointsAction = { elizaLogger.success("fetchDefiOHLCV: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchDefiOHLCVPair"); await birdeyeProvider.fetchDefiOHLCVPair({ ...sampleParams, type: "1D", @@ -117,6 +138,7 @@ export const testAllEndpointsAction = { elizaLogger.success("fetchDefiOHLCVPair: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchDefiOHLCVBaseQuote"); await birdeyeProvider.fetchDefiOHLCVBaseQuote({ ...sampleParams, type: "1D", @@ -124,20 +146,22 @@ export const testAllEndpointsAction = { elizaLogger.success("fetchDefiOHLCVBaseQuote: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchDefiPriceVolume"); await birdeyeProvider.fetchDefiPriceVolume({ - ...sampleParams, - type: "1D", + address: sampleParams.token, }); elizaLogger.success("fetchDefiPriceVolume: SUCCESS!"); await waitFor(500); - await birdeyeProvider.fetchDefiPriceVolumeMulti_POST({ - ...sampleParams, - type: "1D", - }); - elizaLogger.success("fetchDefiPriceVolumeMulti_POST: SUCCESS!"); - await waitFor(500); + // this endpoint is for enterprise users only + // elizaLogger.info("fetchDefiPriceVolumeMulti_POST"); + // await birdeyeProvider.fetchDefiPriceVolumeMulti_POST({ + // list_address: sampleParams.token, + // }); + // elizaLogger.success("fetchDefiPriceVolumeMulti_POST: SUCCESS!"); + // await waitFor(500); + elizaLogger.info("fetchTokenList"); await birdeyeProvider.fetchTokenList({ ...sampleParams, sort_by: "mc", @@ -146,20 +170,24 @@ export const testAllEndpointsAction = { elizaLogger.success("fetchTokenList: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchTokenSecurityByAddress"); await birdeyeProvider.fetchTokenSecurityByAddress({ ...sampleParams, }); elizaLogger.success("fetchTokenSecurityByAddress: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchTokenOverview"); await birdeyeProvider.fetchTokenOverview({ ...sampleParams }); elizaLogger.success("fetchTokenOverview: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchTokenCreationInfo"); await birdeyeProvider.fetchTokenCreationInfo({ ...sampleParams }); elizaLogger.success("fetchTokenCreationInfo: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchTokenTrending"); await birdeyeProvider.fetchTokenTrending({ ...sampleParams, sort_by: "volume24hUSD", @@ -168,23 +196,31 @@ export const testAllEndpointsAction = { elizaLogger.success("fetchTokenTrending: SUCCESS!"); await waitFor(500); - await birdeyeProvider.fetchTokenListV2_POST({}); - elizaLogger.success("fetchTokenListV2_POST: SUCCESS!"); - await waitFor(500); + // this endpoint is for enterprise users only + // elizaLogger.info("fetchTokenListV2_POST"); + // await birdeyeProvider.fetchTokenListV2_POST({}); + // elizaLogger.success("fetchTokenListV2_POST: SUCCESS!"); + // await waitFor(500); - await birdeyeProvider.fetchTokenNewListing({ ...sampleParams }); + elizaLogger.info("fetchTokenNewListing"); + await birdeyeProvider.fetchTokenNewListing({ + time_to: new Date().getTime(), + meme_platform_enabled: true, + }); elizaLogger.success("fetchTokenNewListing: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchTokenTopTraders"); await birdeyeProvider.fetchTokenTopTraders({ ...sampleParams, - time_frame: "12H", + time_frame: "24h", sort_type: "asc", sort_by: "volume", }); elizaLogger.success("fetchTokenTopTraders: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchTokenAllMarketsList"); await birdeyeProvider.fetchTokenAllMarketsList({ ...sampleParams, time_frame: "12H", @@ -194,34 +230,43 @@ export const testAllEndpointsAction = { elizaLogger.success("fetchTokenAllMarketsList: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchTokenMetadataSingle"); await birdeyeProvider.fetchTokenMetadataSingle({ ...sampleParams }); elizaLogger.success("fetchTokenMetadataSingle: SUCCESS!"); await waitFor(500); - await birdeyeProvider.fetchTokenMetadataMulti({ ...sampleParams }); - elizaLogger.success("fetchTokenMetadataMulti: SUCCESS!"); - await waitFor(500); + // this endpoint is for enterprise users only + // elizaLogger.info("fetchTokenMetadataMulti"); + // await birdeyeProvider.fetchTokenMetadataMulti({ ...sampleParams }); + // elizaLogger.success("fetchTokenMetadataMulti: SUCCESS!"); + // await waitFor(500); + elizaLogger.info("fetchTokenMarketData"); await birdeyeProvider.fetchTokenMarketData({ ...sampleParams }); elizaLogger.success("fetchTokenMarketData: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchTokenTradeDataSingle"); await birdeyeProvider.fetchTokenTradeDataSingle({ ...sampleParams, }); elizaLogger.success("fetchTokenTradeDataSingle: SUCCESS!"); await waitFor(500); - await birdeyeProvider.fetchTokenTradeDataMultiple({ - ...sampleParams, - }); - elizaLogger.success("fetchTokenTradeDataMultiple: SUCCESS!"); - await waitFor(500); + // this endpoint is for enterprise users only + // elizaLogger.info("fetchTokenTradeDataMultiple"); + // await birdeyeProvider.fetchTokenTradeDataMultiple({ + // ...sampleParams, + // }); + // elizaLogger.success("fetchTokenTradeDataMultiple: SUCCESS!"); + // await waitFor(500); + elizaLogger.info("fetchTokenHolders"); await birdeyeProvider.fetchTokenHolders({ ...sampleParams }); elizaLogger.success("fetchTokenHolders: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchTokenMintBurn"); await birdeyeProvider.fetchTokenMintBurn({ ...sampleParams, sort_by: "block_time", @@ -231,38 +276,45 @@ export const testAllEndpointsAction = { elizaLogger.success("fetchTokenMintBurn: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchWalletSupportedNetworks"); await birdeyeProvider.fetchWalletSupportedNetworks(); elizaLogger.success("fetchWalletSupportedNetworks: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchWalletPortfolio"); await birdeyeProvider.fetchWalletPortfolio({ ...sampleParams }); elizaLogger.success("fetchWalletPortfolio: SUCCESS!"); await waitFor(500); - await birdeyeProvider.fetchWalletPortfolioMultichain({ - ...sampleParams, - }); - elizaLogger.success("fetchWalletPortfolioMultichain: SUCCESS!"); - await waitFor(500); + // elizaLogger.info("fetchWalletPortfolioMultichain"); + // await birdeyeProvider.fetchWalletPortfolioMultichain({ + // ...sampleParams, + // }); + // elizaLogger.success("fetchWalletPortfolioMultichain: SUCCESS!"); + // await waitFor(500); + elizaLogger.info("fetchWalletTokenBalance"); await birdeyeProvider.fetchWalletTokenBalance({ ...sampleParams }); elizaLogger.success("fetchWalletTokenBalance: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchWalletTransactionHistory"); await birdeyeProvider.fetchWalletTransactionHistory({ ...sampleParams, }); elizaLogger.success("fetchWalletTransactionHistory: SUCCESS!"); await waitFor(500); - await birdeyeProvider.fetchWalletTransactionHistoryMultichain({ - ...sampleParams, - }); - elizaLogger.success( - "fetchWalletTransactionHistoryMultichain: SUCCESS!" - ); - await waitFor(500); + // elizaLogger.info("fetchWalletTransactionHistoryMultichain"); + // await birdeyeProvider.fetchWalletTransactionHistoryMultichain({ + // ...sampleParams, + // }); + // elizaLogger.success( + // "fetchWalletTransactionHistoryMultichain: SUCCESS!" + // ); + // await waitFor(500); + elizaLogger.info("fetchWalletTransactionSimulate_POST"); await birdeyeProvider.fetchWalletTransactionSimulate_POST({ ...sampleParams, }); @@ -271,41 +323,53 @@ export const testAllEndpointsAction = { ); await waitFor(500); + elizaLogger.info("fetchTraderGainersLosers"); await birdeyeProvider.fetchTraderGainersLosers({ ...sampleParams, type: "today", + sort_type: "asc", }); elizaLogger.success("fetchTraderGainersLosers: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchTraderTransactionsSeek"); await birdeyeProvider.fetchTraderTransactionsSeek({ ...sampleParams, + tx_type: "all", + before_time: undefined, }); elizaLogger.success("fetchTraderTransactionsSeek: SUCCESS!"); await waitFor(500); + elizaLogger.info("fetchPairOverviewSingle"); await birdeyeProvider.fetchPairOverviewSingle({ ...sampleParams }); elizaLogger.success("fetchPairOverviewSingle: SUCCESS!"); await waitFor(500); - await birdeyeProvider.fetchMultiPairOverview({ ...sampleParams }); - elizaLogger.success("fetchMultiPairOverview: SUCCESS!"); - await waitFor(500); + // this endpoint is for enterprise users only + // elizaLogger.info("fetchMultiPairOverview"); + // await birdeyeProvider.fetchMultiPairOverview({ ...sampleParams }); + // elizaLogger.success("fetchMultiPairOverview: SUCCESS!"); + // await waitFor(500); - await birdeyeProvider.fetchPairOverviewMultiple({ - ...sampleParams, - }); - elizaLogger.success("fetchPairOverviewMultiple: SUCCESS!"); - await waitFor(500); + // this endpoint is for enterprise users only + // elizaLogger.info("fetchPairOverviewMultiple"); + // await birdeyeProvider.fetchPairOverviewMultiple({ + // ...sampleParams, + // }); + // elizaLogger.success("fetchPairOverviewMultiple: SUCCESS!"); + // await waitFor(500); + elizaLogger.info("fetchSearchTokenMarketData"); await birdeyeProvider.fetchSearchTokenMarketData({ ...sampleParams, + sort_type: "asc", }); elizaLogger.success("fetchSearchTokenMarketData: SUCCESS!"); await waitFor(500); elizaLogger.info("All endpoints tested successfully"); - callback?.({ text: "All endpoints tested successfully" }); + callback?.({ text: "All endpoints tested successfully!" }); return true; } catch (error) { console.error("Error in testAllEndpointsAction:", error.message); diff --git a/packages/plugin-birdeye/src/birdeye.ts b/packages/plugin-birdeye/src/birdeye.ts index 4f911e029a..a1460c4dd9 100644 --- a/packages/plugin-birdeye/src/birdeye.ts +++ b/packages/plugin-birdeye/src/birdeye.ts @@ -38,8 +38,6 @@ import { PairOverviewMultiResponse, PairOverviewSingleParams, PairOverviewSingleResponse, - PairTradesParams, - PairTradesResponse, } from "./types/api/pair"; import { TokenMarketSearchParams, @@ -714,10 +712,10 @@ export class BirdeyeProvider extends BaseCachedProvider { } public async fetchPairOverviewMultiple( - params: FetchParams + params: FetchParams ) { - return this.fetchWithCacheAndRetry({ - url: BIRDEYE_ENDPOINTS.defi.trades_pair, + return this.fetchWithCacheAndRetry({ + url: BIRDEYE_ENDPOINTS.pair.overview_multi, params, headers: params.headers, }); diff --git a/packages/plugin-birdeye/src/constants.ts b/packages/plugin-birdeye/src/constants.ts index f598698c8c..e9b74ad185 100644 --- a/packages/plugin-birdeye/src/constants.ts +++ b/packages/plugin-birdeye/src/constants.ts @@ -58,7 +58,7 @@ export const BIRDEYE_ENDPOINTS = { transaction_simulation_POST: "/v1/wallet/simulate", // https://docs.birdeye.so/reference/post_v1-wallet-simulate }, trader: { - gainers_losers: "/trader/gainers_losers", // https://docs.birdeye.so/reference/get_trader-gainers-losers + gainers_losers: "/trader/gainers-losers", // https://docs.birdeye.so/reference/get_trader-gainers-losers trades_seek: "/trader/txs/seek_by_time", // https://docs.birdeye.so/reference/get_trader-txs-seek-by-time }, pair: { @@ -69,11 +69,3 @@ export const BIRDEYE_ENDPOINTS = { token_market: "/defi/v3/search", // https://docs.birdeye.so/reference/get_defi-v3-search }, }; - -export type BirdeyeEndpoint = - | keyof (typeof BIRDEYE_ENDPOINTS)["defi"] - | keyof (typeof BIRDEYE_ENDPOINTS)["token"] - | keyof (typeof BIRDEYE_ENDPOINTS)["wallet"] - | keyof (typeof BIRDEYE_ENDPOINTS)["trader"] - | keyof (typeof BIRDEYE_ENDPOINTS)["pair"] - | keyof (typeof BIRDEYE_ENDPOINTS)["search"]; diff --git a/packages/plugin-birdeye/src/index.ts b/packages/plugin-birdeye/src/index.ts index f943bee8d4..d066a67d54 100644 --- a/packages/plugin-birdeye/src/index.ts +++ b/packages/plugin-birdeye/src/index.ts @@ -1,16 +1,15 @@ import { Plugin } from "@elizaos/core"; -import { searchTokensBySymbolAction } from "./actions/searchTokens"; -import { searchWalletsAction } from "./actions/searchWallets"; -import { testAllEndpointsAction } from "./actions/test-all-endpoints"; +import { getTokenInfoAction } from "./actions/get-token-info"; +import { getWalletInfoAction } from "./actions/get-wallet-info"; import { agentPortfolioProvider } from "./providers/agent-portfolio-provider"; export const birdeyePlugin: Plugin = { name: "birdeye", description: "Birdeye Plugin for token data and analytics", actions: [ - searchTokensBySymbolAction, - searchWalletsAction, - testAllEndpointsAction, + getTokenInfoAction, + getWalletInfoAction, + // testAllEndpointsAction, // this action can be used to optionally test all endpoints ], evaluators: [], providers: [agentPortfolioProvider], diff --git a/packages/plugin-birdeye/src/providers/agent-portfolio-provider.ts b/packages/plugin-birdeye/src/providers/agent-portfolio-provider.ts index 7ed0f99ffb..6f90f5351c 100644 --- a/packages/plugin-birdeye/src/providers/agent-portfolio-provider.ts +++ b/packages/plugin-birdeye/src/providers/agent-portfolio-provider.ts @@ -30,9 +30,11 @@ export const agentPortfolioProvider: Provider = { const chain = extractChain(walletAddr); - const resp = await provider.fetchSearchTokenMarketData({ - keyword: walletAddr, - chain, + const resp = await provider.fetchWalletPortfolio({ + wallet: walletAddr, + headers: { + chain, + }, }); const portfolioText = formatPortfolio(resp); diff --git a/packages/plugin-birdeye/src/types/api/common.ts b/packages/plugin-birdeye/src/types/api/common.ts index 720d9b1f51..59b5d9903b 100644 --- a/packages/plugin-birdeye/src/types/api/common.ts +++ b/packages/plugin-birdeye/src/types/api/common.ts @@ -103,7 +103,15 @@ export type TimeInterval = | "1D" | "3D" | "1W" - | "1M"; + | "1M" + | "30m" + | "1h" + | "2h" + | "4h" + | "6h" + | "8h" + | "12h" + | "24h"; export interface TokenTradeData { address: string; diff --git a/packages/plugin-birdeye/src/types/api/defi.ts b/packages/plugin-birdeye/src/types/api/defi.ts index c66384a5cd..896991df6c 100644 --- a/packages/plugin-birdeye/src/types/api/defi.ts +++ b/packages/plugin-birdeye/src/types/api/defi.ts @@ -73,7 +73,7 @@ export interface DefiHistoryPriceResponse { // Historical Price Unix Types export interface HistoricalPriceUnixParams { address: string; - unixtime: number; + unixtime?: number; } export interface HistoricalPriceUnixResponse { @@ -112,7 +112,7 @@ export interface OHLCVResponse { // Price Volume Types export interface PriceVolumeParams { address: string; - type: TimeInterval; + type?: TimeInterval; } export interface PriceVolumeResponse { @@ -175,6 +175,7 @@ export interface DefiTradesTokenParams { limit?: number; offset?: number; tx_type?: "swap" | "add" | "remove" | "all"; + sort_type?: "asc" | "desc"; before_time?: number; after_time?: number; } diff --git a/packages/plugin-birdeye/src/types/api/pair.ts b/packages/plugin-birdeye/src/types/api/pair.ts index 5e58cf07f7..91a89c9c36 100644 --- a/packages/plugin-birdeye/src/types/api/pair.ts +++ b/packages/plugin-birdeye/src/types/api/pair.ts @@ -59,6 +59,7 @@ export interface OHLCVPairResponse { // Pair Overview Types export interface PairOverviewMultiParams { list_address: string; + before_time?: number; } export interface PairOverviewSingleParams { diff --git a/packages/plugin-birdeye/src/types/api/token.ts b/packages/plugin-birdeye/src/types/api/token.ts index 6eeeab7675..6922ee2235 100644 --- a/packages/plugin-birdeye/src/types/api/token.ts +++ b/packages/plugin-birdeye/src/types/api/token.ts @@ -552,7 +552,7 @@ export interface NewListingResponse { // Top Traders Types export interface TopTradersParams { address: string; - time_frame: TimeInterval; + time_frame?: TimeInterval; sort_type?: "asc" | "desc"; sort_by?: "volume" | "trade"; offset?: number; diff --git a/packages/plugin-birdeye/src/utils.ts b/packages/plugin-birdeye/src/utils.ts index 4dd4c1d434..21f1606327 100644 --- a/packages/plugin-birdeye/src/utils.ts +++ b/packages/plugin-birdeye/src/utils.ts @@ -2,6 +2,7 @@ import { elizaLogger } from "@elizaos/core"; import { BirdeyeApiParams } from "./types/api/common"; import { TokenMarketSearchResponse, TokenResult } from "./types/api/search"; import { TokenMetadataSingleResponse } from "./types/api/token"; +import { WalletPortfolioResponse } from "./types/api/wallet"; import { BaseAddress, BirdeyeSupportedChain } from "./types/shared"; // Constants @@ -570,16 +571,14 @@ const formatSocialLinks = ( export const waitFor = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); -export const formatPortfolio = (data: TokenMarketSearchResponse) => { - const items = data.data?.items.filter( - (item) => item.type === "token" - ) as TokenResult[]; +export const formatPortfolio = (response: WalletPortfolioResponse) => { + const { items } = response.data; if (!items?.length) return "No tokens found in portfolio"; return items .map((item) => { - const value = item?.price?.toFixed(2); - const amount = item?.liquidity?.toFixed(4); + const value = item?.priceUsd?.toFixed(2); + const amount = item?.uiAmount?.toFixed(4); return ( `ā€¢ ${item.symbol || "Unknown Token"}: ${amount} tokens` + `${value !== "0.00" ? ` (Value: $${value || "unknown"})` : ""}` @@ -597,3 +596,19 @@ export const convertToStringParams = (params: BirdeyeApiParams) => { {} as Record ); }; + +export const getTokenResultFromSearchResponse = ( + response: TokenMarketSearchResponse, + symbol: string +): TokenResult | undefined => { + const tokenResponses: TokenMarketSearchResponse["data"]["items"] = + response.data.items.filter((item) => item.type === "token"); + + return tokenResponses + .map((item) => item.result) + .flat() + .find( + (r: TokenResult): r is TokenResult => + r.symbol.toLowerCase() === symbol.toLowerCase() + ); +}; From 0caf8d30ff2d4c0bfb584eb415fdbd74fc18fe57 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Tue, 31 Dec 2024 14:23:30 -0800 Subject: [PATCH 09/33] chore: cleanup plugin logic --- .../src/actions/get-token-info.ts | 134 ++++++--- .../src/actions/get-wallet-info.ts | 14 +- .../src/actions/test-all-endpoints.ts | 6 +- packages/plugin-birdeye/src/birdeye.ts | 255 +++++++++++------- .../src/providers/agent-portfolio-provider.ts | 14 +- 5 files changed, 278 insertions(+), 145 deletions(-) diff --git a/packages/plugin-birdeye/src/actions/get-token-info.ts b/packages/plugin-birdeye/src/actions/get-token-info.ts index 24d57f0e08..ad3002e7fc 100644 --- a/packages/plugin-birdeye/src/actions/get-token-info.ts +++ b/packages/plugin-birdeye/src/actions/get-token-info.ts @@ -8,25 +8,40 @@ import { } from "@elizaos/core"; import { BirdeyeProvider } from "../birdeye"; import { TokenResult } from "../types/api/search"; +import { + TokenMarketDataResponse, + TokenOverviewResponse, + TokenSecurityResponse, + TokenTradeDataSingleResponse, +} from "../types/api/token"; import { extractChain, extractSymbols, getTokenResultFromSearchResponse, } from "../utils"; -const formatTokenReport = (token, metadata, security, volume) => { +const formatTokenReport = ( + token, + metadata: TokenMarketDataResponse, + security: TokenSecurityResponse, + volume: TokenTradeDataSingleResponse, + overview: TokenOverviewResponse +) => { let output = `*Token Security and Trade Report*\n`; output += `Token symbol: ${token.symbol}\n`; output += `Token Address: ${token.address}\n\n`; if (security?.data) { output += `*Ownership Distribution:*\n`; - output += `- Owner Balance: ${security.data.ownerBalance}\n`; - output += `- Creator Balance: ${security.data.creatorBalance}\n`; - output += `- Owner Percentage: ${security.data.ownerPercentage}%\n`; - output += `- Creator Percentage: ${security.data.creatorPercentage}%\n`; - output += `- Top 10 Holders Balance: ${security.data.top10HolderBalance}\n`; - output += `- Top 10 Holders Percentage: ${security.data.top10HolderPercent}%\n\n`; + output += `- Owner Address: ${security.data.ownerAddress}\n`; + output += `- Creator Address: ${security.data.creatorAddress}\n`; + output += `- Total Supply: ${security.data.totalSupply}\n`; + output += `- Mintable: ${security.data.mintable}\n`; + output += `- Proxied: ${security.data.proxied}\n`; + output += `- Proxy: ${security.data.proxy}\n`; + if (security.data.securityChecks) { + output += `- Security Checks: ${JSON.stringify(security.data.securityChecks)}\n`; + } } if (volume?.data) { @@ -34,17 +49,32 @@ const formatTokenReport = (token, metadata, security, volume) => { output += `- Holders: ${volume.data.holder}\n`; output += `- Unique Wallets (24h): ${volume.data.unique_wallet_24h}\n`; output += `- Price Change (24h): ${volume.data.price_change_24h_percent}%\n`; - output += `- Price Change (12h): ${volume.data.price_change_12h_percent}%\n`; output += `- Volume (24h USD): $${volume.data.volume_24h_usd}\n`; - output += `- Current Price: $${volume.data.price}\n\n`; + output += `- Current Price: $${volume.data.price}\n`; } - if (metadata) { - output += `*Additional Info:*\n`; - output += `- Name: ${metadata.name}\n`; - output += `- Chain: ${metadata.chain}\n`; - if (metadata.website) output += `- Website: ${metadata.website}\n`; - if (metadata.twitter) output += `- Twitter: ${metadata.twitter}\n`; + if (metadata?.data) { + output += `*Market Data:*\n`; + output += `- Liquidity: ${metadata.data.liquidity}\n`; + output += `- Price: ${metadata.data.price}\n`; + output += `- Supply: ${metadata.data.supply}\n`; + output += `- Market Cap: ${metadata.data.marketcap}\n`; + output += `- Circulating Supply: ${metadata.data.circulating_supply}\n`; + output += `- Circulating Market Cap: ${metadata.data.circulating_marketcap}\n`; + } + + if (overview?.data) { + output += `*Overview:*\n`; + output += `- Name: ${overview.data.name}\n`; + output += `- Symbol: ${overview.data.symbol}\n`; + output += `- Decimals: ${overview.data.decimals}\n`; + if (overview.data.extensions) { + output += `- Extensions: ${JSON.stringify(overview.data.extensions)}\n`; + } + output += `- Liquidity: ${overview.data.liquidity}\n`; + output += `- Last Trade Time: ${overview.data.lastTradeHumanTime}\n`; + output += `- Price: ${overview.data.price}\n`; + output += `- Description: ${overview.data.extensions?.description}\n`; } return output; @@ -89,7 +119,7 @@ export const getTokenInfoAction = { try { const provider = new BirdeyeProvider(runtime.cacheManager); - const symbols = extractSymbols(message.content.text, "loose"); + const symbols = extractSymbols(message.content.text, "strict"); if (symbols.length === 0) { callback?.({ text: "No token symbols found in the message" }); @@ -103,7 +133,9 @@ export const getTokenInfoAction = { const searchTokenResponses = symbols.map((symbol) => provider.fetchSearchTokenMarketData({ keyword: symbol, - limit: 1, + sort_by: "volume_24h_usd", + sort_type: "desc", + chain: "all", }) ); @@ -133,27 +165,50 @@ export const getTokenInfoAction = { // Fetch all data in parallel for each token const tokenData = await Promise.all( resultsWithChains.map(async ({ address, chain }) => { - const [metadata, security, volume] = await Promise.all([ - provider.fetchTokenMarketData({ - address, - headers: { - chain, - }, - }), - provider.fetchTokenSecurityByAddress({ - address, - headers: { - chain, - }, - }), - provider.fetchTokenTradeDataSingle({ - address, - headers: { - chain, - }, - }), - ]); - return { metadata, security, volume }; + const [metadata, security, volume, overview] = + await Promise.all([ + provider.fetchTokenMarketData( + { + address, + }, + { + headers: { + chain, + }, + } + ), + provider.fetchTokenSecurityByAddress( + { + address, + }, + { + headers: { + chain, + }, + } + ), + provider.fetchTokenTradeDataSingle( + { + address, + }, + { + headers: { + chain, + }, + } + ), + provider.fetchTokenOverview( + { + address, + }, + { + headers: { + chain, + }, + } + ), + ]); + return { metadata, security, volume, overview }; }) ); @@ -164,7 +219,8 @@ export const getTokenInfoAction = { result!, tokenData[index].metadata, tokenData[index].security, - tokenData[index].volume + tokenData[index].volume, + tokenData[index].overview )}` ) .join("\n\n")}`; diff --git a/packages/plugin-birdeye/src/actions/get-wallet-info.ts b/packages/plugin-birdeye/src/actions/get-wallet-info.ts index 0586b5121a..d1f72f297f 100644 --- a/packages/plugin-birdeye/src/actions/get-wallet-info.ts +++ b/packages/plugin-birdeye/src/actions/get-wallet-info.ts @@ -87,12 +87,16 @@ export const getWalletInfoAction = { // Search Birdeye services for wallet portfolio data const searchAddressesForTokenMatch = addresses.map((address) => - provider.fetchWalletPortfolio({ - wallet: address.address, - headers: { - chain: address.chain, + provider.fetchWalletPortfolio( + { + wallet: address.address, }, - }) + { + headers: { + chain: address.chain, + }, + } + ) ); const results = await Promise.all(searchAddressesForTokenMatch); diff --git a/packages/plugin-birdeye/src/actions/test-all-endpoints.ts b/packages/plugin-birdeye/src/actions/test-all-endpoints.ts index f2c1dcb485..9c23b653bc 100644 --- a/packages/plugin-birdeye/src/actions/test-all-endpoints.ts +++ b/packages/plugin-birdeye/src/actions/test-all-endpoints.ts @@ -32,7 +32,6 @@ export const testAllEndpointsAction = { token: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm", address: "MfDuWeqSHEqTFVYZ7LoexgAK9dxk7cy4DFJWjWMGVWa", network: "solana", - headers: { "x-chain": "solana" }, list_address: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm", address_type: "token", type: "1D", @@ -316,7 +315,10 @@ export const testAllEndpointsAction = { elizaLogger.info("fetchWalletTransactionSimulate_POST"); await birdeyeProvider.fetchWalletTransactionSimulate_POST({ - ...sampleParams, + from: sampleParams.token, + to: sampleParams.token, + data: JSON.stringify({ test: "ok" }), + value: "100000", }); elizaLogger.success( "fetchWalletTransactionSimulate_POST: SUCCESS!" diff --git a/packages/plugin-birdeye/src/birdeye.ts b/packages/plugin-birdeye/src/birdeye.ts index a1460c4dd9..a1e55bce88 100644 --- a/packages/plugin-birdeye/src/birdeye.ts +++ b/packages/plugin-birdeye/src/birdeye.ts @@ -1,4 +1,4 @@ -import { ICacheManager, settings } from "@elizaos/core"; +import { elizaLogger, ICacheManager, settings } from "@elizaos/core"; import NodeCache from "node-cache"; import * as path from "path"; import { @@ -240,17 +240,19 @@ export class BirdeyeProvider extends BaseCachedProvider { const val = await this.readFromCache(cacheKey); if (val) return val as T; - const data = await this.fetchWithRetry( + const urlWithParams = method === "GET" && params ? `${fullUrl}?${new URLSearchParams(stringParams)}` - : fullUrl, - { - method, - headers, - ...(method === "POST" && - params && { body: JSON.stringify(params) }), - } - ); + : fullUrl; + + elizaLogger.info(`Birdeye fetch: ${urlWithParams}`); + + const data = await this.fetchWithRetry(urlWithParams, { + method, + headers, + ...(method === "POST" && + params && { body: JSON.stringify(params) }), + }); await this.writeToCache(cacheKey, data); return data as T; @@ -268,147 +270,171 @@ export class BirdeyeProvider extends BaseCachedProvider { } // Get price update of a token. - public async fetchDefiPrice(params: FetchParams) { + public async fetchDefiPrice( + params: DefiPriceParams, + options: { headers?: Record } = {} + ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.defi.price, params, - headers: params.headers, + headers: options.headers, }); } // Get price updates of multiple tokens in a single API call. Maximum 100 tokens public async fetchDefiPriceMultiple( - params: FetchParams + params: DefiMultiPriceParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.defi.price_multi, params, - headers: params.headers, + headers: options.headers, }); } // Get price updates of multiple tokens in a single API call. Maximum 100 tokens public async fetchDefiPriceMultiple_POST( - params: FetchParams + params: DefiMultiPriceParamsPOST, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.defi.price_multi_POST, params, - headers: params.headers, + headers: options.headers, method: "POST", }); } // Get historical price line chart of a token. public async fetchDefiPriceHistorical( - params: FetchParams + params: DefiHistoryPriceParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.defi.history_price, params, - headers: params.headers, + headers: options.headers, }); } // Get historical price by unix timestamp public async fetchDefiPriceHistoricalByUnixTime( - params: FetchParams + params: HistoricalPriceUnixParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.defi.historical_price_unix, params, - headers: params.headers, + headers: options.headers, }); } // Get list of trades of a certain token. public async fetchDefiTradesToken( - params: FetchParams + params: DefiTradesTokenParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.defi.trades_token, params, - headers: params.headers, + headers: options.headers, }); } // Get list of trades of a certain pair or market. public async fetchDefiTradesPair( - params: FetchParams + params: DefiTradesTokenParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.defi.trades_token, params, - headers: params.headers, + headers: options.headers, }); } // Get list of trades of a token with time bound option. public async fetchDefiTradesTokenSeekByTime( - params: FetchParams + params: DefiTradesTokenParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.defi.trades_token_seek, params, - headers: params.headers, + headers: options.headers, }); } // Get list of trades of a certain pair or market with time bound option. public async fetchDefiTradesPairSeekByTime( - params: FetchParams + params: DefiTradesTokenParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.defi.trades_pair_seek, params, - headers: params.headers, + headers: options.headers, }); } // Get OHLCV price of a token. - public async fetchDefiOHLCV(params: FetchParams) { + public async fetchDefiOHLCV( + params: OHLCVParams, + options: { headers?: Record } = {} + ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.defi.ohlcv, params, - headers: params.headers, + headers: options.headers, }); } // Get OHLCV price of a pair. - public async fetchDefiOHLCVPair(params: FetchParams) { + public async fetchDefiOHLCVPair( + params: OHLCVPairParams, + options: { headers?: Record } = {} + ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.defi.ohlcv_pair, params, - headers: params.headers, + headers: options.headers, }); } // Get OHLCV price of a base-quote pair. - public async fetchDefiOHLCVBaseQuote(params: FetchParams) { + public async fetchDefiOHLCVBaseQuote( + params: BaseQuoteParams, + options: { headers?: Record } = {} + ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.defi.ohlcv_base_quote, params, - headers: params.headers, + headers: options.headers, }); } // Get price and volume of a token. - public async fetchDefiPriceVolume(params: FetchParams) { + public async fetchDefiPriceVolume( + params: PriceVolumeParams, + options: { headers?: Record } = {} + ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.defi.price_volume, params, - headers: params.headers, + headers: options.headers, }); } // Get price and volume updates of maximum 50 tokens public async fetchDefiPriceVolumeMulti_POST( - params: FetchParams + params: MultiPriceVolumeParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.defi.price_volume_multi_POST, params, - headers: params.headers, + headers: options.headers, method: "POST", }); } @@ -418,51 +444,62 @@ export class BirdeyeProvider extends BaseCachedProvider { */ // Get token list of any supported chains. - public async fetchTokenList(params: FetchParams) { + public async fetchTokenList( + params: TokenListParams, + options: { headers?: Record } = {} + ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.token.list_all, params, - headers: params.headers, + headers: options.headers, }); } // Get token security of any supported chains. public async fetchTokenSecurityByAddress( - params: FetchParams + params: TokenSecurityParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.token.security, params, - headers: params.headers, + headers: options.headers, }); } // Get overview of a token. - public async fetchTokenOverview(params: FetchParams) { + public async fetchTokenOverview( + params: TokenOverviewParams, + options: { headers?: Record } = {} + ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.token.overview, params, - headers: params.headers, + headers: options.headers, }); } // Get creation info of token public async fetchTokenCreationInfo( - params: FetchParams + params: TokenCreationInfoParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.token.creation_info, params, - headers: params.headers, + headers: options.headers, }); } // Retrieve a dynamic and up-to-date list of trending tokens based on specified sorting criteria. - public async fetchTokenTrending(params?: FetchParams) { + public async fetchTokenTrending( + params?: TokenTrendingParams, + options: { headers?: Record } = {} + ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.token.trending, params, - headers: params?.headers, + headers: options.headers, }); } @@ -479,104 +516,122 @@ export class BirdeyeProvider extends BaseCachedProvider { } // Get newly listed tokens of any supported chains. - public async fetchTokenNewListing(params?: FetchParams) { + public async fetchTokenNewListing( + params?: NewListingParams, + options: { headers?: Record } = {} + ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.token.new_listing, params, - headers: params?.headers, + headers: options?.headers, }); } // Get top traders of given token. - public async fetchTokenTopTraders(params: FetchParams) { + public async fetchTokenTopTraders( + params: TopTradersParams, + options: { headers?: Record } = {} + ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.token.top_traders, params, - headers: params.headers, + headers: options.headers, }); } // The API provides detailed information about the markets for a specific cryptocurrency token on a specified blockchain. Users can retrieve data for one or multiple markets related to a single token. This endpoint requires the specification of a token address and the blockchain to filter results. Additionally, it supports optional query parameters such as offset, limit, and required sorting by liquidity or sort type (ascending or descending) to refine the output. public async fetchTokenAllMarketsList( - params: FetchParams + params: AllMarketsParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.token.all_markets, params, - headers: params.headers, + headers: options.headers, }); } // Get metadata of single token public async fetchTokenMetadataSingle( - params: FetchParams + params: TokenMetadataSingleParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.token.metadata_single, params, - headers: params.headers, + headers: options.headers, }); } // Get metadata of multiple tokens public async fetchTokenMetadataMulti( - params: FetchParams + params: TokenMetadataMultiParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.token.metadata_multi, params, - headers: params.headers, + headers: options.headers, }); } // Get market data of single token public async fetchTokenMarketData( - params: FetchParams + params: TokenMarketDataParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.token.market_data, params, - headers: params.headers, + headers: options.headers, }); } // Get trade data of single token public async fetchTokenTradeDataSingle( - params: FetchParams + params: TokenTradeDataSingleParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.token.trade_data_single, params, - headers: params.headers, + headers: options.headers, }); } // Get trade data of multiple tokens public async fetchTokenTradeDataMultiple( - params: FetchParams + params: TokenTradeDataMultiParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.token.trade_data_multi, params, - headers: params.headers, + headers: options.headers, }); } // Get top holder list of the given token - public async fetchTokenHolders(params: FetchParams) { + public async fetchTokenHolders( + params: TokenHoldersParams, + options: { headers?: Record } = {} + ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.token.holders, params, - headers: params.headers, + headers: options.headers, }); } // Get mint/burn transaction list of the given token. Only support solana currently - public async fetchTokenMintBurn(params: FetchParams) { + public async fetchTokenMintBurn( + params: MintBurnParams, + options: { headers?: Record } = {} + ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.token.mint_burn, params, - headers: params.headers, + headers: options.headers, }); } @@ -584,21 +639,22 @@ export class BirdeyeProvider extends BaseCachedProvider { * WALLET FETCH FUNCTIONS */ public async fetchWalletSupportedNetworks( - params?: FetchParams> + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.defi.networks, - headers: params?.headers, + headers: options.headers, }); } public async fetchWalletPortfolio( - params: FetchParams + params: WalletPortfolioParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.wallet.portfolio, params, - headers: params.headers, + headers: options.headers, }); } @@ -606,32 +662,35 @@ export class BirdeyeProvider extends BaseCachedProvider { * @deprecated This endpoint will be decommissioned on Feb 1st, 2025. */ public async fetchWalletPortfolioMultichain( - params: FetchParams + params: WalletPortfolioMultichainParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.wallet.portfolio_multichain, params, - headers: params.headers, + headers: options.headers, }); } public async fetchWalletTokenBalance( - params: FetchParams + params: WalletTokenBalanceParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.wallet.token_balance, params, - headers: params.headers, + headers: options.headers, }); } public async fetchWalletTransactionHistory( - params: FetchParams + params: WalletTransactionHistoryParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.wallet.transaction_history, params, - headers: params.headers, + headers: options.headers, }); } @@ -639,24 +698,26 @@ export class BirdeyeProvider extends BaseCachedProvider { * @deprecated This endpoint will be decommissioned on Feb 1st, 2025. */ public async fetchWalletTransactionHistoryMultichain( - params: FetchParams + params: WalletTransactionHistoryMultichainParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry( { url: BIRDEYE_ENDPOINTS.wallet.transaction_history_multichain, params, - headers: params.headers, + headers: options.headers, } ); } public async fetchWalletTransactionSimulate_POST( - params: FetchParams + params: WalletSimulationParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.wallet.transaction_simulation_POST, params, - headers: params.headers, + headers: options.headers, method: "POST", }); } @@ -667,23 +728,25 @@ export class BirdeyeProvider extends BaseCachedProvider { // The API provides detailed information top gainers/losers public async fetchTraderGainersLosers( - params: FetchParams + params: GainersLosersParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.trader.gainers_losers, params, - headers: params.headers, + headers: options.headers, }); } // Get list of trades of a trader with time bound option. public async fetchTraderTransactionsSeek( - params: FetchParams + params: TraderTransactionsSeekParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.trader.trades_seek, params, - headers: params.headers, + headers: options.headers, }); } @@ -691,33 +754,36 @@ export class BirdeyeProvider extends BaseCachedProvider { * PAIR FETCH FUNCTIONS */ public async fetchPairOverviewSingle( - params: FetchParams + params: PairOverviewSingleParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.pair.overview_single, params, - headers: params.headers, + headers: options.headers, }); } // Get overview of multiple pairs public async fetchMultiPairOverview( - params: FetchParams + params: PairOverviewMultiParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.pair.overview_multi, params, - headers: params.headers, + headers: options.headers, }); } public async fetchPairOverviewMultiple( - params: FetchParams + params: PairOverviewMultiParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.pair.overview_multi, params, - headers: params.headers, + headers: options.headers, }); } @@ -725,12 +791,13 @@ export class BirdeyeProvider extends BaseCachedProvider { * SEARCH FETCH FUNCTIONS */ public async fetchSearchTokenMarketData( - params: FetchParams + params: TokenMarketSearchParams, + options: { headers?: Record } = {} ) { return this.fetchWithCacheAndRetry({ url: BIRDEYE_ENDPOINTS.search.token_market, params, - headers: params.headers, + headers: options.headers, }); } } diff --git a/packages/plugin-birdeye/src/providers/agent-portfolio-provider.ts b/packages/plugin-birdeye/src/providers/agent-portfolio-provider.ts index 6f90f5351c..04460780f2 100644 --- a/packages/plugin-birdeye/src/providers/agent-portfolio-provider.ts +++ b/packages/plugin-birdeye/src/providers/agent-portfolio-provider.ts @@ -30,12 +30,16 @@ export const agentPortfolioProvider: Provider = { const chain = extractChain(walletAddr); - const resp = await provider.fetchWalletPortfolio({ - wallet: walletAddr, - headers: { - chain, + const resp = await provider.fetchWalletPortfolio( + { + wallet: walletAddr, }, - }); + { + headers: { + chain, + }, + } + ); const portfolioText = formatPortfolio(resp); From cfbcd27ba1b9ec680bc892d3b041e2b1193f1c62 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Tue, 31 Dec 2024 14:42:20 -0800 Subject: [PATCH 10/33] fix: chain header bug --- packages/plugin-birdeye/src/actions/get-token-info.ts | 9 +++++---- packages/plugin-birdeye/src/utils.ts | 6 +++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/plugin-birdeye/src/actions/get-token-info.ts b/packages/plugin-birdeye/src/actions/get-token-info.ts index ad3002e7fc..ca6a612772 100644 --- a/packages/plugin-birdeye/src/actions/get-token-info.ts +++ b/packages/plugin-birdeye/src/actions/get-token-info.ts @@ -136,6 +136,7 @@ export const getTokenInfoAction = { sort_by: "volume_24h_usd", sort_type: "desc", chain: "all", + limit: 15, }) ); @@ -173,7 +174,7 @@ export const getTokenInfoAction = { }, { headers: { - chain, + "x-chain": chain, }, } ), @@ -183,7 +184,7 @@ export const getTokenInfoAction = { }, { headers: { - chain, + "x-chain": chain, }, } ), @@ -193,7 +194,7 @@ export const getTokenInfoAction = { }, { headers: { - chain, + "x-chain": chain, }, } ), @@ -203,7 +204,7 @@ export const getTokenInfoAction = { }, { headers: { - chain, + "x-chain": chain, }, } ), diff --git a/packages/plugin-birdeye/src/utils.ts b/packages/plugin-birdeye/src/utils.ts index 21f1606327..dd7f7716dd 100644 --- a/packages/plugin-birdeye/src/utils.ts +++ b/packages/plugin-birdeye/src/utils.ts @@ -609,6 +609,10 @@ export const getTokenResultFromSearchResponse = ( .flat() .find( (r: TokenResult): r is TokenResult => - r.symbol.toLowerCase() === symbol.toLowerCase() + r.symbol.toLowerCase() === symbol.toLowerCase() && + // only show tokens with liquidity, fdv, and price to help filter out junk + Boolean(r.liquidity) && + Boolean(r.fdv) && + Boolean(r.price) ); }; From 9f18e24d3b8682cd22f2f95e4351073977711eca Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Tue, 31 Dec 2024 14:47:12 -0800 Subject: [PATCH 11/33] chore: fix get token info format --- .../src/actions/get-token-info.ts | 77 ++++++++++--------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/packages/plugin-birdeye/src/actions/get-token-info.ts b/packages/plugin-birdeye/src/actions/get-token-info.ts index ca6a612772..641a9dcb8f 100644 --- a/packages/plugin-birdeye/src/actions/get-token-info.ts +++ b/packages/plugin-birdeye/src/actions/get-token-info.ts @@ -2,9 +2,9 @@ import { Action, ActionExample, elizaLogger, - type IAgentRuntime, - type Memory, - type State, + IAgentRuntime, + Memory, + State, } from "@elizaos/core"; import { BirdeyeProvider } from "../birdeye"; import { TokenResult } from "../types/api/search"; @@ -17,7 +17,12 @@ import { import { extractChain, extractSymbols, + formatPercentChange, + formatPrice, + formatTimestamp, + formatValue, getTokenResultFromSearchResponse, + shortenAddress, } from "../utils"; const formatTokenReport = ( @@ -27,54 +32,54 @@ const formatTokenReport = ( volume: TokenTradeDataSingleResponse, overview: TokenOverviewResponse ) => { - let output = `*Token Security and Trade Report*\n`; - output += `Token symbol: ${token.symbol}\n`; - output += `Token Address: ${token.address}\n\n`; + let output = `*šŸ›”ļø Token Security and Trade Report*\n`; + output += `šŸ”– Token symbol: ${token.symbol}\n`; + output += `šŸ”— Token Address: ${shortenAddress(token.address)}\n\n`; if (security?.data) { - output += `*Ownership Distribution:*\n`; - output += `- Owner Address: ${security.data.ownerAddress}\n`; - output += `- Creator Address: ${security.data.creatorAddress}\n`; - output += `- Total Supply: ${security.data.totalSupply}\n`; - output += `- Mintable: ${security.data.mintable}\n`; - output += `- Proxied: ${security.data.proxied}\n`; - output += `- Proxy: ${security.data.proxy}\n`; + output += `*šŸ‘„ Ownership Distribution:*\n`; + output += `- šŸ  Owner Address: ${shortenAddress(security.data.ownerAddress)}\n`; + output += `- šŸ‘Øā€šŸ’¼ Creator Address: ${shortenAddress(security.data.creatorAddress)}\n`; + output += `- šŸ“¦ Total Supply: ${formatValue(security.data.totalSupply)}\n`; + output += `- Mintable: ${security.data.mintable ?? "N/A"}\n`; + output += `- šŸ”„ Proxied: ${security.data.proxied ?? "N/A"}\n`; + output += `- šŸ”„ Proxy: ${security.data.proxy ?? "N/A"}\n`; if (security.data.securityChecks) { - output += `- Security Checks: ${JSON.stringify(security.data.securityChecks)}\n`; + output += `- šŸ” Security Checks: ${JSON.stringify(security.data.securityChecks)}\n`; } } if (volume?.data) { - output += `*Trade Data:*\n`; - output += `- Holders: ${volume.data.holder}\n`; - output += `- Unique Wallets (24h): ${volume.data.unique_wallet_24h}\n`; - output += `- Price Change (24h): ${volume.data.price_change_24h_percent}%\n`; - output += `- Volume (24h USD): $${volume.data.volume_24h_usd}\n`; - output += `- Current Price: $${volume.data.price}\n`; + output += `*šŸ“ˆ Trade Data:*\n`; + output += `- šŸ‘„ Holders: ${volume.data.holder}\n`; + output += `- šŸ“Š Unique Wallets (24h): ${volume.data.unique_wallet_24h}\n`; + output += `- šŸ“‰ Price Change (24h): ${formatPercentChange(volume.data.price_change_24h_percent)}\n`; + output += `- šŸ’ø Volume (24h USD): ${formatValue(volume.data.volume_24h_usd)}\n`; + output += `- šŸ’µ Current Price: ${formatPrice(volume.data.price)}\n`; } if (metadata?.data) { - output += `*Market Data:*\n`; - output += `- Liquidity: ${metadata.data.liquidity}\n`; - output += `- Price: ${metadata.data.price}\n`; - output += `- Supply: ${metadata.data.supply}\n`; - output += `- Market Cap: ${metadata.data.marketcap}\n`; - output += `- Circulating Supply: ${metadata.data.circulating_supply}\n`; - output += `- Circulating Market Cap: ${metadata.data.circulating_marketcap}\n`; + output += `*šŸ“Š Market Data:*\n`; + output += `- šŸ’§ Liquidity: ${formatValue(metadata.data.liquidity)}\n`; + output += `- šŸ’µ Price: ${formatPrice(metadata.data.price)}\n`; + output += `- šŸ“¦ Supply: ${formatValue(metadata.data.supply)}\n`; + output += `- šŸ’° Market Cap: ${formatValue(metadata.data.marketcap)}\n`; + output += `- šŸ”„ Circulating Supply: ${formatValue(metadata.data.circulating_supply)}\n`; + output += `- šŸ’° Circulating Market Cap: ${formatValue(metadata.data.circulating_marketcap)}\n`; } if (overview?.data) { - output += `*Overview:*\n`; - output += `- Name: ${overview.data.name}\n`; - output += `- Symbol: ${overview.data.symbol}\n`; - output += `- Decimals: ${overview.data.decimals}\n`; + output += `*šŸ” Overview:*\n`; + output += `- šŸ“ Name: ${overview.data.name}\n`; + output += `- šŸ”– Symbol: ${overview.data.symbol}\n`; + output += `- šŸ”¢ Decimals: ${overview.data.decimals}\n`; if (overview.data.extensions) { - output += `- Extensions: ${JSON.stringify(overview.data.extensions)}\n`; + output += `- šŸ”— Extensions: ${JSON.stringify(overview.data.extensions)}\n`; } - output += `- Liquidity: ${overview.data.liquidity}\n`; - output += `- Last Trade Time: ${overview.data.lastTradeHumanTime}\n`; - output += `- Price: ${overview.data.price}\n`; - output += `- Description: ${overview.data.extensions?.description}\n`; + output += `- šŸ’§ Liquidity: ${formatValue(overview.data.liquidity)}\n`; + output += `- ā° Last Trade Time: ${formatTimestamp(new Date(overview.data.lastTradeHumanTime).getTime() / 1000)}\n`; + output += `- šŸ’µ Price: ${formatPrice(overview.data.price)}\n`; + output += `- šŸ“œ Description: ${overview.data.extensions?.description ?? "N/A"}\n`; } return output; From bb7ddc649a842680509742f78cfae08da7f9db6a Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Tue, 31 Dec 2024 14:58:01 -0800 Subject: [PATCH 12/33] chore: cleanup output format --- .../src/actions/get-token-info.ts | 78 ++++++++++--------- .../plugin-birdeye/src/types/api/search.ts | 44 +++++------ packages/plugin-birdeye/src/utils.ts | 19 +++-- 3 files changed, 75 insertions(+), 66 deletions(-) diff --git a/packages/plugin-birdeye/src/actions/get-token-info.ts b/packages/plugin-birdeye/src/actions/get-token-info.ts index 641a9dcb8f..1c274a0bec 100644 --- a/packages/plugin-birdeye/src/actions/get-token-info.ts +++ b/packages/plugin-birdeye/src/actions/get-token-info.ts @@ -26,60 +26,64 @@ import { } from "../utils"; const formatTokenReport = ( - token, - metadata: TokenMarketDataResponse, - security: TokenSecurityResponse, - volume: TokenTradeDataSingleResponse, - overview: TokenOverviewResponse + token: TokenResult | undefined, + metadata: TokenMarketDataResponse | undefined, + security: TokenSecurityResponse | undefined, + volume: TokenTradeDataSingleResponse | undefined, + overview: TokenOverviewResponse | undefined ) => { let output = `*šŸ›”ļø Token Security and Trade Report*\n`; - output += `šŸ”– Token symbol: ${token.symbol}\n`; - output += `šŸ”— Token Address: ${shortenAddress(token.address)}\n\n`; + output += `šŸ”– Token symbol: ${token?.symbol}\n`; + output += `šŸ”— Token Address: ${shortenAddress(token?.address)}\n\n`; if (security?.data) { + output += `\n`; output += `*šŸ‘„ Ownership Distribution:*\n`; - output += `- šŸ  Owner Address: ${shortenAddress(security.data.ownerAddress)}\n`; - output += `- šŸ‘Øā€šŸ’¼ Creator Address: ${shortenAddress(security.data.creatorAddress)}\n`; - output += `- šŸ“¦ Total Supply: ${formatValue(security.data.totalSupply)}\n`; - output += `- Mintable: ${security.data.mintable ?? "N/A"}\n`; - output += `- šŸ”„ Proxied: ${security.data.proxied ?? "N/A"}\n`; - output += `- šŸ”„ Proxy: ${security.data.proxy ?? "N/A"}\n`; + output += `šŸ  Owner Address: ${shortenAddress(security.data.ownerAddress)}\n`; + output += `šŸ‘Øā€šŸ’¼ Creator Address: ${shortenAddress(security.data.creatorAddress)}\n`; + output += `šŸ“¦ Total Supply: ${formatValue(security.data.totalSupply)}\n`; + output += ` Mintable: ${security.data.mintable ?? "N/A"}\n`; + output += `šŸ”„ Proxied: ${security.data.proxied ?? "N/A"}\n`; + output += `šŸ”„ Proxy: ${security.data.proxy ?? "N/A"}\n`; if (security.data.securityChecks) { - output += `- šŸ” Security Checks: ${JSON.stringify(security.data.securityChecks)}\n`; + output += `šŸ” Security Checks: ${JSON.stringify(security.data.securityChecks)}\n`; } } if (volume?.data) { + output += `\n`; output += `*šŸ“ˆ Trade Data:*\n`; - output += `- šŸ‘„ Holders: ${volume.data.holder}\n`; - output += `- šŸ“Š Unique Wallets (24h): ${volume.data.unique_wallet_24h}\n`; - output += `- šŸ“‰ Price Change (24h): ${formatPercentChange(volume.data.price_change_24h_percent)}\n`; - output += `- šŸ’ø Volume (24h USD): ${formatValue(volume.data.volume_24h_usd)}\n`; - output += `- šŸ’µ Current Price: ${formatPrice(volume.data.price)}\n`; + output += `šŸ‘„ Holders: ${volume.data.holder}\n`; + output += `šŸ“Š Unique Wallets (24h): ${volume.data.unique_wallet_24h}\n`; + output += `šŸ“‰ Price Change (24h): ${formatPercentChange(volume.data.price_change_24h_percent)}\n`; + output += `šŸ’ø Volume (24h USD): ${formatValue(volume.data.volume_24h_usd)}\n`; + output += `šŸ’µ Current Price: ${formatPrice(volume.data.price)}\n`; } if (metadata?.data) { + output += `\n`; output += `*šŸ“Š Market Data:*\n`; - output += `- šŸ’§ Liquidity: ${formatValue(metadata.data.liquidity)}\n`; - output += `- šŸ’µ Price: ${formatPrice(metadata.data.price)}\n`; - output += `- šŸ“¦ Supply: ${formatValue(metadata.data.supply)}\n`; - output += `- šŸ’° Market Cap: ${formatValue(metadata.data.marketcap)}\n`; - output += `- šŸ”„ Circulating Supply: ${formatValue(metadata.data.circulating_supply)}\n`; - output += `- šŸ’° Circulating Market Cap: ${formatValue(metadata.data.circulating_marketcap)}\n`; + output += `šŸ’§ Liquidity: ${formatValue(metadata.data.liquidity)}\n`; + output += `šŸ’µ Price: ${formatPrice(metadata.data.price)}\n`; + output += `šŸ“¦ Supply: ${formatValue(metadata.data.supply)}\n`; + output += `šŸ’° Market Cap: ${formatValue(metadata.data.marketcap)}\n`; + output += `šŸ”„ Circulating Supply: ${formatValue(metadata.data.circulating_supply)}\n`; + output += `šŸ’° Circulating Market Cap: ${formatValue(metadata.data.circulating_marketcap)}\n`; } if (overview?.data) { + output += `\n`; output += `*šŸ” Overview:*\n`; - output += `- šŸ“ Name: ${overview.data.name}\n`; - output += `- šŸ”– Symbol: ${overview.data.symbol}\n`; - output += `- šŸ”¢ Decimals: ${overview.data.decimals}\n`; + output += `šŸ“ Name: ${overview.data.name}\n`; + output += `šŸ”– Symbol: ${overview.data.symbol}\n`; + output += `šŸ”¢ Decimals: ${overview.data.decimals}\n`; if (overview.data.extensions) { - output += `- šŸ”— Extensions: ${JSON.stringify(overview.data.extensions)}\n`; + output += `šŸ”— Extensions: ${JSON.stringify(overview.data.extensions)}\n`; } - output += `- šŸ’§ Liquidity: ${formatValue(overview.data.liquidity)}\n`; - output += `- ā° Last Trade Time: ${formatTimestamp(new Date(overview.data.lastTradeHumanTime).getTime() / 1000)}\n`; - output += `- šŸ’µ Price: ${formatPrice(overview.data.price)}\n`; - output += `- šŸ“œ Description: ${overview.data.extensions?.description ?? "N/A"}\n`; + output += `šŸ’§ Liquidity: ${formatValue(overview.data.liquidity)}\n`; + output += `ā° Last Trade Time: ${formatTimestamp(new Date(overview.data.lastTradeHumanTime).getTime() / 1000)}\n`; + output += `šŸ’µ Price: ${formatPrice(overview.data.price)}\n`; + output += `šŸ“œ Description: ${overview.data.extensions?.description ?? "N/A"}\n`; } return output; @@ -223,10 +227,10 @@ export const getTokenInfoAction = { (result, index) => `${formatTokenReport( result!, - tokenData[index].metadata, - tokenData[index].security, - tokenData[index].volume, - tokenData[index].overview + tokenData[index]?.metadata, + tokenData[index]?.security, + tokenData[index]?.volume, + tokenData[index]?.overview )}` ) .join("\n\n")}`; diff --git a/packages/plugin-birdeye/src/types/api/search.ts b/packages/plugin-birdeye/src/types/api/search.ts index d4235bd065..dd1ab8d520 100644 --- a/packages/plugin-birdeye/src/types/api/search.ts +++ b/packages/plugin-birdeye/src/types/api/search.ts @@ -40,28 +40,28 @@ export interface TokenMarketSearchResponse { } export interface TokenResult { - name: string; - symbol: string; - address: string; - fdv: number; - market_cap: number; - liquidity: number; - volume_24h_change_percent: number; - price: number; - price_change_24h_percent: number; - buy_24h: number; - buy_24h_change_percent: number; - sell_24h: number; - sell_24h_change_percent: number; - trade_24h: number; - trade_24h_change_percent: number; - unique_wallet_24h: number; - unique_view_24h_change_percent: number; - last_trade_human_time: string; - last_trade_unix_time: number; - creation_time: string; - volume_24h_usd: number; - logo_uri: string; + name?: string; + symbol?: string; + address?: string; + fdv?: number; + market_cap?: number; + liquidity?: number; + volume_24h_change_percent?: number; + price?: number; + price_change_24h_percent?: number; + buy_24h?: number; + buy_24h_change_percent?: number; + sell_24h?: number; + sell_24h_change_percent?: number; + trade_24h?: number; + trade_24h_change_percent?: number; + unique_wallet_24h?: number; + unique_view_24h_change_percent?: number; + last_trade_human_time?: string; + last_trade_unix_time?: number; + creation_time?: string; + volume_24h_usd?: number; + logo_uri?: string; } export interface MarketResult { diff --git a/packages/plugin-birdeye/src/utils.ts b/packages/plugin-birdeye/src/utils.ts index dd7f7716dd..a553792ef4 100644 --- a/packages/plugin-birdeye/src/utils.ts +++ b/packages/plugin-birdeye/src/utils.ts @@ -284,8 +284,9 @@ export const extractLimit = (text: string): number => { }; // Formatting helpers -export const formatValue = (value: number): string => { - if (value >= 1_000_000_000) { +export const formatValue = (value?: number): string => { + if (!value) return "N/A"; + if (value && value >= 1_000_000_000) { return `$${(value / 1_000_000_000).toFixed(2)}B`; } if (value >= 1_000_000) { @@ -303,17 +304,21 @@ export const formatPercentChange = (change?: number): string => { return `${symbol} ${Math.abs(change).toFixed(2)}%`; }; -export const shortenAddress = (address: string): string => { +export const shortenAddress = (address?: string): string => { if (!address || address.length <= 12) return address || "Unknown"; return `${address.slice(0, 6)}...${address.slice(-4)}`; }; -export const formatTimestamp = (timestamp: number): string => { - return new Date(timestamp * 1000).toLocaleString(); +export const formatTimestamp = (timestamp?: number): string => { + return timestamp ? new Date(timestamp * 1000).toLocaleString() : "N/A"; }; -export const formatPrice = (price: number): string => { - return price < 0.01 ? price.toExponential(2) : price.toFixed(2); +export const formatPrice = (price?: number): string => { + return price + ? price < 0.01 + ? price.toExponential(2) + : price.toFixed(2) + : "N/A"; }; // API helpers From 5c5bdd1d130b61697aea7309f46b7da8945cb796 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Tue, 31 Dec 2024 15:01:50 -0800 Subject: [PATCH 13/33] chore: removed unwanted changes --- .vscode/launch.json | 3 +-- client/src/Chat.tsx | 12 +++++------- packages/core/src/defaultCharacter.ts | 2 +- scripts/dev.sh | 2 +- src/providers/address-search.provider.ts | 9 --------- 5 files changed, 8 insertions(+), 20 deletions(-) delete mode 100644 src/providers/address-search.provider.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 0fbdde8983..373d7d18a0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,6 @@ "request": "launch", "name": "Launch via pnpm", "runtimeExecutable": "pnpm", - "runtimeVersion": "23.3.0", "runtimeArgs": [ "run", "dev" @@ -17,6 +16,6 @@ "skipFiles": [ "/**" ] - }, + } ] } \ No newline at end of file diff --git a/client/src/Chat.tsx b/client/src/Chat.tsx index f077935167..b32cc0b83e 100644 --- a/client/src/Chat.tsx +++ b/client/src/Chat.tsx @@ -1,8 +1,8 @@ -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { useMutation } from "@tanstack/react-query"; import { useState } from "react"; import { useParams } from "react-router-dom"; +import { useMutation } from "@tanstack/react-query"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; import "./App.css"; type TextResponse = { @@ -58,7 +58,7 @@ export default function Chat() { messages.map((message, index) => (
-
-                                        {message.text}
-                                    
+ {message.text}
)) diff --git a/packages/core/src/defaultCharacter.ts b/packages/core/src/defaultCharacter.ts index 17dd8860de..91cdeba925 100644 --- a/packages/core/src/defaultCharacter.ts +++ b/packages/core/src/defaultCharacter.ts @@ -5,7 +5,7 @@ export const defaultCharacter: Character = { username: "eliza", plugins: [], clients: [], - modelProvider: ModelProviderName.ANTHROPIC, + modelProvider: ModelProviderName.LLAMALOCAL, settings: { secrets: {}, voice: { diff --git a/scripts/dev.sh b/scripts/dev.sh index 9ed45c99eb..22b5466f9d 100644 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -74,7 +74,7 @@ if [ ! -d "$PACKAGES_DIR" ]; then fi # List of working folders to watch (relative to $PACKAGES_DIR) -WORKING_FOLDERS=("client-direct" "plugin-birdeye") # Core is handled separately +WORKING_FOLDERS=("client-direct") # Core is handled separately # Initialize an array to hold package-specific commands COMMANDS=() diff --git a/src/providers/address-search.provider.ts b/src/providers/address-search.provider.ts deleted file mode 100644 index 7e6f2f4342..0000000000 --- a/src/providers/address-search.provider.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Searches message text for contract addresses, symbols, or wallet addresses and enriches them with: - * - Portfolio data from wallet addresses - * - Token metadata from contract addresses/symbols - * Queries endpoints in parallel and aggregates results for agent context. - */ -export class AddressSearchProvider { - // ... rest of code -} From 76a3c63ee40b94a0ee015b9453090e82062cfd80 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Tue, 31 Dec 2024 15:03:24 -0800 Subject: [PATCH 14/33] chore: undo changes --- .../plugin-solana/src/evaluators/trust.ts | 9 +---- packages/plugin-solana/src/index.ts | 17 ++++---- .../src/providers/trustScoreProvider.ts | 39 ++++++++----------- 3 files changed, 26 insertions(+), 39 deletions(-) diff --git a/packages/plugin-solana/src/evaluators/trust.ts b/packages/plugin-solana/src/evaluators/trust.ts index 1837cff970..f47918171e 100644 --- a/packages/plugin-solana/src/evaluators/trust.ts +++ b/packages/plugin-solana/src/evaluators/trust.ts @@ -3,7 +3,6 @@ import { booleanFooter, composeContext, Content, - elizaLogger, Evaluator, generateObjectArray, generateTrueOrFalse, @@ -81,13 +80,6 @@ Response should be a JSON object array inside a JSON markdown block. Correct res async function handler(runtime: IAgentRuntime, message: Memory) { console.log("Evaluating for trust"); - - // if the database type is postgres, we don't want to run this because it relies on sql queries that are currently specific to sqlite. This check can be removed once the trust score provider is updated to work with postgres. - if (runtime.getSetting("POSTGRES_URL")) { - elizaLogger.warn("skipping trust evaluator because db is postgres"); - return []; - } - const state = await runtime.composeState(message); const { agentId, roomId } = state; @@ -194,6 +186,7 @@ async function handler(runtime: IAgentRuntime, message: Memory) { } // create the trust score manager + const trustScoreDb = new TrustScoreDatabase(runtime.databaseAdapter.db); const trustScoreManager = new TrustScoreManager( runtime, diff --git a/packages/plugin-solana/src/index.ts b/packages/plugin-solana/src/index.ts index cd632ee8e0..b207ed260b 100644 --- a/packages/plugin-solana/src/index.ts +++ b/packages/plugin-solana/src/index.ts @@ -1,18 +1,20 @@ -export * from "./evaluators/trust.ts"; export * from "./providers/token.ts"; -export * from "./providers/trustScoreProvider.ts"; export * from "./providers/wallet.ts"; +export * from "./providers/trustScoreProvider.ts"; +export * from "./evaluators/trust.ts"; import { Plugin } from "@elizaos/core"; -import fomo from "./actions/fomo.ts"; -import pumpfun from "./actions/pumpfun.ts"; import { executeSwap } from "./actions/swap.ts"; -import { executeSwapForDAO } from "./actions/swapDao"; import take_order from "./actions/takeOrder"; +import pumpfun from "./actions/pumpfun.ts"; +import fomo from "./actions/fomo.ts"; +import { executeSwapForDAO } from "./actions/swapDao"; import transferToken from "./actions/transfer.ts"; +import { walletProvider } from "./providers/wallet.ts"; +import { trustScoreProvider } from "./providers/trustScoreProvider.ts"; import { trustEvaluator } from "./evaluators/trust.ts"; import { TokenProvider } from "./providers/token.ts"; -import { walletProvider, WalletProvider } from "./providers/wallet.ts"; +import { WalletProvider } from "./providers/wallet.ts"; export { TokenProvider, WalletProvider }; @@ -28,8 +30,7 @@ export const solanaPlugin: Plugin = { take_order, ], evaluators: [trustEvaluator], - // providers: [walletProvider, trustScoreProvider], - providers: [walletProvider], + providers: [walletProvider, trustScoreProvider], }; export default solanaPlugin; diff --git a/packages/plugin-solana/src/providers/trustScoreProvider.ts b/packages/plugin-solana/src/providers/trustScoreProvider.ts index 4825511706..931cd9b44d 100644 --- a/packages/plugin-solana/src/providers/trustScoreProvider.ts +++ b/packages/plugin-solana/src/providers/trustScoreProvider.ts @@ -1,25 +1,26 @@ import { - elizaLogger, - IAgentRuntime, - Memory, - Provider, - settings, - State, -} from "@elizaos/core"; + ProcessedTokenData, + TokenSecurityData, + // TokenTradeData, + // DexScreenerData, + // DexScreenerPair, + // HolderData, +} from "../types/token.ts"; +import { Connection, PublicKey } from "@solana/web3.js"; +import { getAssociatedTokenAddress } from "@solana/spl-token"; +import { TokenProvider } from "./token.ts"; +import { WalletProvider } from "./wallet.ts"; +import { SimulationSellingService } from "./simulationSellingService.ts"; import { + TrustScoreDatabase, RecommenderMetrics, TokenPerformance, - TokenRecommendation, TradePerformance, - TrustScoreDatabase, + TokenRecommendation, } from "@elizaos/plugin-trustdb"; -import { getAssociatedTokenAddress } from "@solana/spl-token"; -import { Connection, PublicKey } from "@solana/web3.js"; +import { settings } from "@elizaos/core"; +import { IAgentRuntime, Memory, Provider, State } from "@elizaos/core"; import { v4 as uuidv4 } from "uuid"; -import { ProcessedTokenData, TokenSecurityData } from "../types/token.ts"; -import { SimulationSellingService } from "./simulationSellingService.ts"; -import { TokenProvider } from "./token.ts"; -import { WalletProvider } from "./wallet.ts"; const Wallet = settings.MAIN_WALLET_ADDRESS; interface TradeData { @@ -701,14 +702,6 @@ export const trustScoreProvider: Provider = { _state?: State ): Promise { try { - // if the database type is postgres, we don't want to run this because it relies on sql queries that are currently specific to sqlite. This check can be removed once the trust score provider is updated to work with postgres. - if (runtime.getSetting("POSTGRES_URL")) { - elizaLogger.warn( - "skipping trust evaluator because db is postgres" - ); - return ""; - } - const trustScoreDb = new TrustScoreDatabase( runtime.databaseAdapter.db ); From 513f1f97ce033604de924acf3b85f7595b83f3ec Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Tue, 31 Dec 2024 15:04:14 -0800 Subject: [PATCH 15/33] chore: fix order --- packages/plugin-solana/src/evaluators/trust.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/plugin-solana/src/evaluators/trust.ts b/packages/plugin-solana/src/evaluators/trust.ts index f47918171e..2c4f441cf5 100644 --- a/packages/plugin-solana/src/evaluators/trust.ts +++ b/packages/plugin-solana/src/evaluators/trust.ts @@ -1,22 +1,22 @@ import { - ActionExample, - booleanFooter, composeContext, - Content, - Evaluator, generateObjectArray, generateTrueOrFalse, + MemoryManager, + booleanFooter, + ActionExample, + Content, IAgentRuntime, Memory, - MemoryManager, ModelClass, + Evaluator, } from "@elizaos/core"; +import { TrustScoreManager } from "../providers/trustScoreProvider.ts"; +import { TokenProvider } from "../providers/token.ts"; +import { WalletProvider } from "../providers/wallet.ts"; import { TrustScoreDatabase } from "@elizaos/plugin-trustdb"; import { Connection } from "@solana/web3.js"; import { getWalletKey } from "../keypairUtils.ts"; -import { TokenProvider } from "../providers/token.ts"; -import { TrustScoreManager } from "../providers/trustScoreProvider.ts"; -import { WalletProvider } from "../providers/wallet.ts"; const shouldProcessTemplate = `# Task: Decide if the recent messages should be processed for token recommendations. From 3878f36eb801bc1191c3b174cea5d4a23281e951 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Tue, 31 Dec 2024 15:04:56 -0800 Subject: [PATCH 16/33] chore: revert changes to package --- agent/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/agent/package.json b/agent/package.json index faed1c3efd..be8a3e0e29 100644 --- a/agent/package.json +++ b/agent/package.json @@ -33,7 +33,6 @@ "@elizaos/plugin-abstract": "workspace:*", "@elizaos/plugin-aptos": "workspace:*", "@elizaos/plugin-bootstrap": "workspace:*", - "@elizaos/plugin-birdeye": "workspace:*", "@elizaos/plugin-intiface": "workspace:*", "@elizaos/plugin-coinbase": "workspace:*", "@elizaos/plugin-conflux": "workspace:*", @@ -61,4 +60,4 @@ "ts-node": "10.9.2", "tsup": "8.3.5" } -} \ No newline at end of file +} From 449dff4c40a7f0e738d7f05b93ffcef7f052ed13 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Wed, 1 Jan 2025 10:41:37 -0800 Subject: [PATCH 17/33] fix: launch.json --- .vscode/launch.json | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 373d7d18a0..30a1891bdc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,13 +9,8 @@ "request": "launch", "name": "Launch via pnpm", "runtimeExecutable": "pnpm", - "runtimeArgs": [ - "run", - "dev" - ], - "skipFiles": [ - "/**" - ] + "runtimeArgs": ["run", "dev"], + "skipFiles": ["/**"] } ] -} \ No newline at end of file +} From 742247f24a4389114b2c0b3ca125675b6b93187e Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Thu, 2 Jan 2025 16:16:33 -0800 Subject: [PATCH 18/33] chore: update index --- agent/src/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/agent/src/index.ts b/agent/src/index.ts index a3b4d5aac6..c97553328a 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -13,12 +13,14 @@ import { CacheManager, CacheStore, Character, + Client, Clients, DbCacheAdapter, defaultCharacter, elizaLogger, FsCacheAdapter, IAgentRuntime, + ICacheManager, IDatabaseAdapter, IDatabaseCacheAdapter, ModelProviderName, @@ -524,7 +526,6 @@ export async function createAgent( ? confluxPlugin : null, nodePlugin, - getSecret(character, "BIRDEYE_API_KEY") ? birdeyePlugin : null, getSecret(character, "SOLANA_PUBLIC_KEY") || (getSecret(character, "WALLET_PUBLIC_KEY") && !getSecret(character, "WALLET_PUBLIC_KEY")?.startsWith("0x")) @@ -600,6 +601,7 @@ export async function createAgent( getSecret(character, "AVALANCHE_PRIVATE_KEY") ? avalanchePlugin : null, + getSecret(character, "BIRDEYE_API_KEY") ? birdeyePlugin : null, ].filter(Boolean), providers: [], actions: [], From 1ea19066d2e2bdd53af49fcfd9b5c6cd5ddb3906 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Thu, 2 Jan 2025 16:58:12 -0800 Subject: [PATCH 19/33] chore: fix index --- agent/src/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/agent/src/index.ts b/agent/src/index.ts index c97553328a..8989e26e1c 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -13,14 +13,12 @@ import { CacheManager, CacheStore, Character, - Client, Clients, DbCacheAdapter, defaultCharacter, elizaLogger, FsCacheAdapter, IAgentRuntime, - ICacheManager, IDatabaseAdapter, IDatabaseCacheAdapter, ModelProviderName, From 4feb33fb83c8bf3c2aac8c2e6b65ca3bd9e44a14 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Fri, 3 Jan 2025 16:11:34 -0800 Subject: [PATCH 20/33] chore: update package.json --- agent/package.json | 149 ++-- pnpm-lock.yaml | 1719 ++++++++++++++++++++++++-------------------- 2 files changed, 1033 insertions(+), 835 deletions(-) diff --git a/agent/package.json b/agent/package.json index 48b3f4270a..f5f03d1d7e 100644 --- a/agent/package.json +++ b/agent/package.json @@ -1,75 +1,76 @@ { - "name": "@elizaos/agent", - "version": "0.1.7-alpha.2", - "main": "src/index.ts", - "type": "module", - "scripts": { - "start": "node --loader ts-node/esm src/index.ts", - "dev": "node --loader ts-node/esm src/index.ts", - "check-types": "tsc --noEmit", - "test": "jest" - }, - "nodemonConfig": { - "watch": [ - "src", - "../core/dist" - ], - "ext": "ts,json", - "exec": "node --enable-source-maps --loader ts-node/esm src/index.ts" - }, - "dependencies": { - "@elizaos/adapter-postgres": "workspace:*", - "@elizaos/adapter-redis": "workspace:*", - "@elizaos/adapter-sqlite": "workspace:*", - "@elizaos/client-auto": "workspace:*", - "@elizaos/client-direct": "workspace:*", - "@elizaos/client-discord": "workspace:*", - "@elizaos/client-farcaster": "workspace:*", - "@elizaos/client-lens": "workspace:*", - "@elizaos/client-telegram": "workspace:*", - "@elizaos/client-twitter": "workspace:*", - "@elizaos/client-slack": "workspace:*", - "@elizaos/core": "workspace:*", - "@elizaos/plugin-0g": "workspace:*", - "@elizaos/plugin-abstract": "workspace:*", - "@elizaos/plugin-aptos": "workspace:*", - "@elizaos/plugin-bootstrap": "workspace:*", - "@elizaos/plugin-intiface": "workspace:*", - "@elizaos/plugin-coinbase": "workspace:*", - "@elizaos/plugin-conflux": "workspace:*", - "@elizaos/plugin-evm": "workspace:*", - "@elizaos/plugin-echochambers": "workspace:*", - "@elizaos/plugin-flow": "workspace:*", - "@elizaos/plugin-gitbook": "workspace:*", - "@elizaos/plugin-story": "workspace:*", - "@elizaos/plugin-goat": "workspace:*", - "@elizaos/plugin-icp": "workspace:*", - "@elizaos/plugin-image-generation": "workspace:*", - "@elizaos/plugin-nft-generation": "workspace:*", - "@elizaos/plugin-node": "workspace:*", - "@elizaos/plugin-solana": "workspace:*", - "@elizaos/plugin-starknet": "workspace:*", - "@elizaos/plugin-ton": "workspace:*", - "@elizaos/plugin-sui": "workspace:*", - "@elizaos/plugin-tee": "workspace:*", - "@elizaos/plugin-multiversx": "workspace:*", - "@elizaos/plugin-near": "workspace:*", - "@elizaos/plugin-zksync-era": "workspace:*", - "@elizaos/plugin-twitter": "workspace:*", - "@elizaos/plugin-cronoszkevm": "workspace:*", - "@elizaos/plugin-3d-generation": "workspace:*", - "@elizaos/plugin-fuel": "workspace:*", - "@elizaos/plugin-avalanche": "workspace:*", - "@elizaos/plugin-web-search": "workspace:*", - "readline": "1.3.0", - "ws": "8.18.0", - "yargs": "17.7.2" - }, - "devDependencies": { - "@types/jest": "^29.5.14", - "jest": "^29.7.0", - "ts-jest": "^29.2.5", - "ts-node": "10.9.2", - "tsup": "8.3.5" - } -} + "name": "@elizaos/agent", + "version": "0.1.7-alpha.2", + "main": "src/index.ts", + "type": "module", + "scripts": { + "start": "node --loader ts-node/esm src/index.ts", + "dev": "node --loader ts-node/esm src/index.ts", + "check-types": "tsc --noEmit", + "test": "jest" + }, + "nodemonConfig": { + "watch": [ + "src", + "../core/dist" + ], + "ext": "ts,json", + "exec": "node --enable-source-maps --loader ts-node/esm src/index.ts" + }, + "dependencies": { + "@elizaos/adapter-postgres": "workspace:*", + "@elizaos/adapter-redis": "workspace:*", + "@elizaos/adapter-sqlite": "workspace:*", + "@elizaos/client-auto": "workspace:*", + "@elizaos/client-direct": "workspace:*", + "@elizaos/client-discord": "workspace:*", + "@elizaos/client-farcaster": "workspace:*", + "@elizaos/client-lens": "workspace:*", + "@elizaos/client-telegram": "workspace:*", + "@elizaos/client-twitter": "workspace:*", + "@elizaos/client-slack": "workspace:*", + "@elizaos/core": "workspace:*", + "@elizaos/plugin-0g": "workspace:*", + "@elizaos/plugin-abstract": "workspace:*", + "@elizaos/plugin-aptos": "workspace:*", + "@elizaos/plugin-bootstrap": "workspace:*", + "@elizaos/plugin-birdeye": "workspace:*", + "@elizaos/plugin-intiface": "workspace:*", + "@elizaos/plugin-coinbase": "workspace:*", + "@elizaos/plugin-conflux": "workspace:*", + "@elizaos/plugin-evm": "workspace:*", + "@elizaos/plugin-echochambers": "workspace:*", + "@elizaos/plugin-flow": "workspace:*", + "@elizaos/plugin-gitbook": "workspace:*", + "@elizaos/plugin-story": "workspace:*", + "@elizaos/plugin-goat": "workspace:*", + "@elizaos/plugin-icp": "workspace:*", + "@elizaos/plugin-image-generation": "workspace:*", + "@elizaos/plugin-nft-generation": "workspace:*", + "@elizaos/plugin-node": "workspace:*", + "@elizaos/plugin-solana": "workspace:*", + "@elizaos/plugin-starknet": "workspace:*", + "@elizaos/plugin-ton": "workspace:*", + "@elizaos/plugin-sui": "workspace:*", + "@elizaos/plugin-tee": "workspace:*", + "@elizaos/plugin-multiversx": "workspace:*", + "@elizaos/plugin-near": "workspace:*", + "@elizaos/plugin-zksync-era": "workspace:*", + "@elizaos/plugin-twitter": "workspace:*", + "@elizaos/plugin-cronoszkevm": "workspace:*", + "@elizaos/plugin-3d-generation": "workspace:*", + "@elizaos/plugin-fuel": "workspace:*", + "@elizaos/plugin-avalanche": "workspace:*", + "@elizaos/plugin-web-search": "workspace:*", + "readline": "1.3.0", + "ws": "8.18.0", + "yargs": "17.7.2" + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "ts-node": "10.9.2", + "tsup": "8.3.5" + } +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90f147c715..36c9857b56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,7 +22,7 @@ importers: version: 3.9.0(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) '@vitest/eslint-plugin': specifier: 1.0.1 - version: 1.0.1(@typescript-eslint/utils@8.16.0(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3)(vitest@2.1.5(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + version: 1.0.1(@typescript-eslint/utils@8.16.0(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3)(vitest@2.1.5(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) amqplib: specifier: 0.10.5 version: 0.10.5 @@ -47,7 +47,7 @@ importers: devDependencies: '@commitlint/cli': specifier: 18.6.1 - version: 18.6.1(@types/node@22.10.4)(typescript@5.6.3) + version: 18.6.1(@types/node@22.10.5)(typescript@5.6.3) '@commitlint/config-conventional': specifier: 18.6.3 version: 18.6.3 @@ -95,10 +95,10 @@ importers: version: 2.21.58(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) vite: specifier: 5.4.11 - version: 5.4.11(@types/node@22.10.4)(terser@5.37.0) + version: 5.4.11(@types/node@22.10.5)(terser@5.37.0) vitest: specifier: 2.1.5 - version: 2.1.5(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) + version: 2.1.5(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) agent: dependencies: @@ -153,6 +153,9 @@ importers: '@elizaos/plugin-avalanche': specifier: workspace:* version: link:../packages/plugin-avalanche + '@elizaos/plugin-birdeye': + specifier: workspace:* + version: link:../packages/plugin-birdeye '@elizaos/plugin-bootstrap': specifier: workspace:* version: link:../packages/plugin-bootstrap @@ -246,16 +249,16 @@ importers: version: 29.5.14 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) + version: 29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)) ts-jest: specifier: ^29.2.5 - version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.24.2)(jest@29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)))(typescript@5.6.3) + version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.24.2)(jest@29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)))(typescript@5.7.2) ts-node: specifier: 10.9.2 - version: 10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3) + version: 10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2) tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) client: dependencies: @@ -355,22 +358,22 @@ importers: dependencies: '@docusaurus/core': specifier: 3.6.3 - version: 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/plugin-content-blog': specifier: 3.6.3 - version: 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10))(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/plugin-content-docs': specifier: 3.6.3 - version: 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/plugin-ideal-image': specifier: 3.6.3 - version: 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/preset-classic': specifier: 3.6.3 - version: 3.6.3(@algolia/client-search@5.18.0)(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/react@18.3.12)(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 3.6.3(@algolia/client-search@5.18.0)(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/react@18.3.12)(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/theme-mermaid': specifier: 3.6.3 - version: 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10))(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) '@mdx-js/react': specifier: 3.0.1 version: 3.0.1(@types/react@18.3.12)(react@18.3.1) @@ -379,7 +382,7 @@ importers: version: 2.1.1 docusaurus-lunr-search: specifier: 3.5.0 - version: 3.5.0(@docusaurus/core@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 3.5.0(@docusaurus/core@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) dotenv: specifier: ^16.4.7 version: 16.4.7 @@ -404,13 +407,13 @@ importers: version: 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) docusaurus-plugin-typedoc: specifier: 1.0.5 - version: 1.0.5(typedoc-plugin-markdown@4.2.10(typedoc@0.26.11(typescript@5.6.3))) + version: 1.0.5(typedoc-plugin-markdown@4.2.10(typedoc@0.26.11(typescript@5.7.2))) typedoc: specifier: 0.26.11 - version: 0.26.11(typescript@5.6.3) + version: 0.26.11(typescript@5.7.2) typedoc-plugin-markdown: specifier: 4.2.10 - version: 4.2.10(typedoc@0.26.11(typescript@5.6.3)) + version: 4.2.10(typedoc@0.26.11(typescript@5.7.2)) packages/adapter-postgres: dependencies: @@ -426,7 +429,7 @@ importers: devDependencies: tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/adapter-redis: dependencies: @@ -445,7 +448,7 @@ importers: version: 5.0.0 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/adapter-sqlite: dependencies: @@ -467,7 +470,7 @@ importers: devDependencies: tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/adapter-sqljs: dependencies: @@ -489,7 +492,7 @@ importers: devDependencies: tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/adapter-supabase: dependencies: @@ -505,7 +508,7 @@ importers: devDependencies: tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/client-auto: dependencies: @@ -536,7 +539,7 @@ importers: devDependencies: tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/client-direct: dependencies: @@ -579,7 +582,7 @@ importers: version: 1.4.12 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/client-discord: dependencies: @@ -616,7 +619,7 @@ importers: devDependencies: tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/client-farcaster: dependencies: @@ -625,11 +628,11 @@ importers: version: link:../core '@neynar/nodejs-sdk': specifier: ^2.0.3 - version: 2.7.0(bufferutil@4.0.9)(class-transformer@0.5.1)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + version: 2.7.0(bufferutil@4.0.9)(class-transformer@0.5.1)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) devDependencies: tsup: specifier: ^8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/client-github: dependencies: @@ -654,7 +657,7 @@ importers: version: 8.1.0 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/client-lens: dependencies: @@ -673,7 +676,7 @@ importers: devDependencies: tsup: specifier: ^8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/client-slack: dependencies: @@ -719,22 +722,22 @@ importers: version: 18.19.69 jest: specifier: ^29.5.0 - version: 29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3)) + version: 29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2)) rimraf: specifier: ^5.0.0 version: 5.0.10 ts-jest: specifier: ^29.1.0 - version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3)))(typescript@5.6.3) + version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2)))(typescript@5.7.2) ts-node: specifier: ^10.9.1 - version: 10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3) + version: 10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2) tsup: specifier: ^8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) typescript: specifier: ^5.0.0 - version: 5.6.3 + version: 5.7.2 packages/client-telegram: dependencies: @@ -753,7 +756,7 @@ importers: devDependencies: tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/client-twitter: dependencies: @@ -775,7 +778,7 @@ importers: devDependencies: tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/core: dependencies: @@ -966,7 +969,7 @@ importers: version: 2.4.0 unbuild: specifier: 2.0.0 - version: 2.0.0(typescript@5.6.3) + version: 2.0.0(typescript@5.7.2) packages/plugin-0g: dependencies: @@ -981,7 +984,7 @@ importers: version: 6.13.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/plugin-3d-generation: dependencies: @@ -990,7 +993,7 @@ importers: version: link:../core tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1002,10 +1005,10 @@ importers: version: link:../core tsup: specifier: ^8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) web3: specifier: ^4.15.0 - version: 4.16.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + version: 4.16.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1032,10 +1035,10 @@ importers: version: 5.1.2 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) vitest: specifier: 2.1.4 - version: 2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) + version: 2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1051,7 +1054,65 @@ importers: devDependencies: tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) + + packages/plugin-birdeye: + dependencies: + '@coral-xyz/anchor': + specifier: 0.30.1 + version: 0.30.1(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@elizaos/core': + specifier: workspace:* + version: link:../core + '@solana/spl-token': + specifier: 0.4.9 + version: 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@solana/web3.js': + specifier: 1.95.8 + version: 1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) + bignumber: + specifier: 1.1.0 + version: 1.1.0 + bignumber.js: + specifier: 9.1.2 + version: 9.1.2 + bs58: + specifier: 6.0.0 + version: 6.0.0 + fomo-sdk-solana: + specifier: 1.3.2 + version: 1.3.2(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) + form-data: + specifier: 4.0.1 + version: 4.0.1 + node-cache: + specifier: 5.1.2 + version: 5.1.2 + pumpdotfun-sdk: + specifier: 1.3.2 + version: 1.3.2(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(rollup@4.29.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + tsup: + specifier: 8.3.5 + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) + vitest: + specifier: 2.1.4 + version: 2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) + whatwg-url: + specifier: 7.1.0 + version: 7.1.0 + devDependencies: + '@types/node': + specifier: ^22.10.2 + version: 22.10.5 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2) + tsconfig-paths: + specifier: ^4.2.0 + version: 4.2.0 + typescript: + specifier: ^5.7.2 + version: 5.7.2 packages/plugin-bootstrap: dependencies: @@ -1060,7 +1121,7 @@ importers: version: link:../core tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1091,7 +1152,7 @@ importers: version: 20.17.9 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/plugin-conflux: dependencies: @@ -1100,7 +1161,7 @@ importers: version: link:../core cive: specifier: 0.7.1 - version: 0.7.1(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 0.7.1(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10) packages/plugin-cronoszkevm: dependencies: @@ -1112,13 +1173,13 @@ importers: version: link:../plugin-trustdb tsup: specifier: ^8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) web3: specifier: ^4.15.0 - version: 4.16.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + version: 4.16.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) web3-plugin-zksync: specifier: ^1.0.8 - version: 1.0.8(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.4)(typescript@5.6.3))(typescript@5.6.3)(utf-8-validate@5.0.10)(web3@4.16.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) + version: 1.0.8(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2))(typescript@5.7.2)(utf-8-validate@5.0.10)(web3@4.16.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8)) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1145,13 +1206,13 @@ importers: version: 5.15.5 '@lifi/sdk': specifier: 3.4.1 - version: 3.4.1(@solana/wallet-adapter-base@0.9.23(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10)))(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(typescript@5.6.3)(viem@2.21.58(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) + version: 3.4.1(@solana/wallet-adapter-base@0.9.23(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10)))(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(typescript@5.7.2)(viem@2.21.58(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8)) '@lifi/types': specifier: 16.3.0 version: 16.3.0 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1163,7 +1224,7 @@ importers: version: 0.1.7-alpha.2(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.27(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(axios@1.7.9)(encoding@0.1.13)(react@18.3.1)(sswr@2.1.0(svelte@5.16.1))(svelte@5.16.1) tsup: specifier: ^8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) ws: specifier: ^8.18.0 version: 8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -1222,10 +1283,10 @@ importers: version: 10.0.0 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) vitest: specifier: 2.1.4 - version: 2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) + version: 2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) packages/plugin-fuel: dependencies: @@ -1237,13 +1298,13 @@ importers: version: 4.0.1 fuels: specifier: 0.97.2 - version: 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + version: 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) vitest: specifier: 2.1.4 - version: 2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) + version: 2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1255,7 +1316,7 @@ importers: version: link:../core tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/plugin-goat: dependencies: @@ -1264,28 +1325,28 @@ importers: version: link:../core '@goat-sdk/adapter-vercel-ai': specifier: 0.2.0 - version: 0.2.0(@goat-sdk/core@0.4.0)(ai@3.4.33(openai@4.77.0(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.16.1))(svelte@5.16.1)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8)) + version: 0.2.0(@goat-sdk/core@0.4.0)(ai@3.4.33(openai@4.77.3(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.16.1))(svelte@5.16.1)(vue@3.5.13(typescript@5.7.2))(zod@3.23.8)) '@goat-sdk/core': specifier: 0.4.0 version: 0.4.0 '@goat-sdk/plugin-erc20': specifier: 0.2.2 - version: 0.2.2(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.21.53(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) + version: 0.2.2(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(viem@2.21.53(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8)) '@goat-sdk/plugin-kim': specifier: 0.1.2 - version: 0.1.2(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.21.53(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) + version: 0.1.2(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(viem@2.21.53(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8)) '@goat-sdk/wallet-evm': specifier: 0.2.0 - version: 0.2.0(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 0.2.0(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10) '@goat-sdk/wallet-viem': specifier: 0.2.0 - version: 0.2.0(@goat-sdk/wallet-evm@0.2.0(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10))(viem@2.21.53(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) + version: 0.2.0(@goat-sdk/wallet-evm@0.2.0(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10))(viem@2.21.53(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8)) tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) viem: specifier: 2.21.53 - version: 2.21.53(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + version: 2.21.53(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1313,7 +1374,7 @@ importers: version: 29.5.14 jest: specifier: 29.7.0 - version: 29.7.0(@types/node@22.10.4) + version: 29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)) tsup: specifier: 8.3.5 version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) @@ -1328,7 +1389,7 @@ importers: version: link:../core tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1346,7 +1407,7 @@ importers: version: 1.0.2 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1373,10 +1434,10 @@ importers: version: 2.1.1 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) vitest: specifier: 2.1.5 - version: 2.1.5(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) + version: 2.1.5(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1403,7 +1464,7 @@ importers: version: 5.1.2 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1433,7 +1494,7 @@ importers: version: 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.5(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(encoding@0.1.13) '@solana-developers/helpers': specifier: ^2.5.6 - version: 2.5.6(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 2.5.6(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) '@solana/web3.js': specifier: 1.95.5 version: 1.95.5(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) @@ -1448,7 +1509,7 @@ importers: version: 5.1.2 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1457,10 +1518,10 @@ importers: dependencies: '@aws-sdk/client-s3': specifier: ^3.705.0 - version: 3.721.0 + version: 3.722.0 '@aws-sdk/s3-request-presigner': specifier: ^3.705.0 - version: 3.721.0 + version: 3.722.0 '@cliqz/adblocker-playwright': specifier: 1.34.0 version: 1.34.0(playwright@1.48.2) @@ -1565,7 +1626,7 @@ importers: version: 5.1.2 node-llama-cpp: specifier: 3.1.1 - version: 3.1.1(typescript@5.6.3) + version: 3.1.1(typescript@5.7.2) nodejs-whisper: specifier: 0.1.18 version: 0.1.18 @@ -1583,10 +1644,10 @@ importers: version: 5.4.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) puppeteer-extra: specifier: 3.3.6 - version: 3.3.6(puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10))(puppeteer@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)) + version: 3.3.6(puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10))(puppeteer@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)) puppeteer-extra-plugin-capsolver: specifier: 2.0.1 - version: 2.0.1(bufferutil@4.0.9)(encoding@0.1.13)(puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10))(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 2.0.1(bufferutil@4.0.9)(encoding@0.1.13)(puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10))(typescript@5.7.2)(utf-8-validate@5.0.10) sharp: specifier: 0.33.5 version: 0.33.5 @@ -1629,7 +1690,7 @@ importers: version: 22.8.4 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/plugin-solana: dependencies: @@ -1647,7 +1708,7 @@ importers: version: link:../plugin-trustdb '@solana/spl-token': specifier: 0.4.9 - version: 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) '@solana/web3.js': specifier: 1.95.8 version: 1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) @@ -1662,7 +1723,7 @@ importers: version: 6.0.0 fomo-sdk-solana: specifier: 1.3.2 - version: 1.3.2(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 1.3.2(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) form-data: specifier: 4.0.1 version: 4.0.1 @@ -1671,13 +1732,13 @@ importers: version: 5.1.2 pumpdotfun-sdk: specifier: 1.3.2 - version: 1.3.2(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(rollup@4.29.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 1.3.2(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(rollup@4.29.1)(typescript@5.7.2)(utf-8-validate@5.0.10) tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) vitest: specifier: 2.1.4 - version: 2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) + version: 2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1704,13 +1765,13 @@ importers: version: 6.18.0(encoding@0.1.13) tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) unruggable-sdk: specifier: 1.4.0 version: 1.4.0(starknet@6.18.0(encoding@0.1.13)) vitest: specifier: 2.1.5 - version: 2.1.5(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) + version: 2.1.5(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1728,17 +1789,17 @@ importers: version: 2.1.0 '@story-protocol/core-sdk': specifier: 1.2.0-rc.3 - version: 1.2.0-rc.3(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + version: 1.2.0-rc.3(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 devDependencies: '@types/node': specifier: ^22.10.1 - version: 22.10.4 + version: 22.10.5 packages/plugin-sui: dependencies: @@ -1750,7 +1811,7 @@ importers: version: link:../plugin-trustdb '@mysten/sui': specifier: ^1.16.0 - version: 1.18.0(typescript@5.6.3) + version: 1.18.0(typescript@5.7.2) bignumber: specifier: 1.1.0 version: 1.1.0 @@ -1765,10 +1826,10 @@ importers: version: 5.1.2 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) vitest: specifier: 2.1.4 - version: 2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) + version: 2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1780,10 +1841,10 @@ importers: version: link:../core '@phala/dstack-sdk': specifier: 0.1.6 - version: 0.1.6(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + version: 0.1.6(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) '@solana/spl-token': specifier: 0.4.9 - version: 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) '@solana/web3.js': specifier: 1.95.8 version: 1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) @@ -1801,10 +1862,10 @@ importers: version: 5.1.2 pumpdotfun-sdk: specifier: 1.3.2 - version: 1.3.2(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(rollup@4.29.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 1.3.2(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(rollup@4.29.1)(typescript@5.7.2)(utf-8-validate@5.0.10) tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1834,7 +1895,7 @@ importers: version: 5.1.2 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1849,13 +1910,13 @@ importers: version: 3.2.2 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) uuid: specifier: 11.0.3 version: 11.0.3 vitest: specifier: 2.1.5 - version: 2.1.5(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) + version: 2.1.5(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1874,7 +1935,7 @@ importers: version: 0.0.17 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) packages/plugin-video-generation: dependencies: @@ -1883,7 +1944,7 @@ importers: version: link:../core tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1895,7 +1956,7 @@ importers: version: link:../core tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1923,10 +1984,10 @@ importers: version: 8.16.0(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3) jest: specifier: 29.7.0 - version: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) + version: 29.7.0(@types/node@20.17.9) ts-jest: specifier: 29.2.5 - version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.24.2)(jest@29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)))(typescript@5.6.3) + version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.9))(typescript@5.6.3) typescript: specifier: 5.6.3 version: 5.6.3 @@ -1941,13 +2002,13 @@ importers: version: link:../plugin-trustdb tsup: specifier: ^8.3.5 - version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0) web3: specifier: ^4.15.0 - version: 4.16.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + version: 4.16.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) web3-plugin-zksync: specifier: ^1.0.8 - version: 1.0.8(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.4)(typescript@5.6.3))(typescript@5.6.3)(utf-8-validate@5.0.10)(web3@4.16.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) + version: 1.0.8(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2))(typescript@5.7.2)(utf-8-validate@5.0.10)(web3@4.16.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8)) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -2306,8 +2367,8 @@ packages: resolution: {integrity: sha512-SCTx9DKOnfEKyWb6bx5J7aeowBig8QmiqOJlE0sMM/pbpF70YGC/ugk1/yFJAJlAkoDadtRvseFpwLvrg7N73Q==} engines: {node: '>=16.0.0'} - '@aws-sdk/client-s3@3.721.0': - resolution: {integrity: sha512-uCZC8elYhUFF21yq1yB5TrE/VYz8A4/VnttUHc65/jqnHReTDvEC0XAc756tJnjfrReyM1ws12FzBLHoW/NDjg==} + '@aws-sdk/client-s3@3.722.0': + resolution: {integrity: sha512-FttdkB39TKjqEITfZJcs6Ihh6alICsNEne0ouLvh8re+gAuTK96zWcfX22mP5ap1QEsATaOGRNsMnyfsDSM0zw==} engines: {node: '>=16.0.0'} '@aws-sdk/client-sso-oidc@3.721.0': @@ -2424,8 +2485,8 @@ packages: resolution: {integrity: sha512-HJzsQxgMOAzZrbf/YIqEx30or4tZK1oNAk6Wm6xecUQx+23JXIaePRu1YFUOLBBERQ4QBPpISFurZWBMZ5ibAw==} engines: {node: '>=16.0.0'} - '@aws-sdk/s3-request-presigner@3.721.0': - resolution: {integrity: sha512-2ibKGssj2TAQyfthNihhBqWdwowlol9bDpKybIi2T6D8l2L9g0ENGLNE50MYzSFAQ3LcjzcvLQ/GByRPiuK+pQ==} + '@aws-sdk/s3-request-presigner@3.722.0': + resolution: {integrity: sha512-dh4yYywf3tHCUwIU8eAQdoFXsjWcssoQXTKoqaqwqRh4WxwuaiRiw6dmD0Q5m6sPsLiHrTPemmDXsF4mLdjmsQ==} engines: {node: '>=16.0.0'} '@aws-sdk/signature-v4-multi-region@3.716.0': @@ -5861,8 +5922,8 @@ packages: resolution: {integrity: sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==} engines: {node: '>= 18'} - '@octokit/auth-oauth-device@7.1.1': - resolution: {integrity: sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==} + '@octokit/auth-oauth-device@7.1.2': + resolution: {integrity: sha512-gTOIzDeV36OhVfxCl69FmvJix7tJIiU6dlxuzLVAzle7fYfO8UDyddr9B+o4CFQVaMBLMGZ9ak2CWMYcGeZnPw==} engines: {node: '>= 18'} '@octokit/auth-oauth-user@5.1.1': @@ -5893,8 +5954,8 @@ packages: resolution: {integrity: sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==} engines: {node: '>= 18'} - '@octokit/core@6.1.2': - resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==} + '@octokit/core@6.1.3': + resolution: {integrity: sha512-z+j7DixNnfpdToYsOutStDgeRzJSMnbj8T1C/oQjB6Aa+kRfNjs/Fn7W6c8bmlt6mfy3FkgeKBRnDjxQow5dow==} engines: {node: '>= 18'} '@octokit/endpoint@10.1.2': @@ -6955,26 +7016,26 @@ packages: resolution: {integrity: sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==} engines: {node: '>=6'} - '@shikijs/core@1.25.1': - resolution: {integrity: sha512-0j5k3ZkLTQViOuNzPVyWGoW1zgH3kiFdUT/JOCkTm7TU74mz+dF+NID+YoiCBzHQxgsDpcGYPjKDJRcuVLSt4A==} + '@shikijs/core@1.26.1': + resolution: {integrity: sha512-yeo7sG+WZQblKPclUOKRPwkv1PyoHYkJ4gP9DzhFJbTdueKR7wYTI1vfF/bFi1NTgc545yG/DzvVhZgueVOXMA==} - '@shikijs/engine-javascript@1.25.1': - resolution: {integrity: sha512-zQ7UWKnRCfD/Q1M+XOSyjsbhpE0qv8LUnmn82HYCeOsgAHgUZGEDIQ63bbuK3kU5sQg+2CtI+dPfOqD/mjSY9w==} + '@shikijs/engine-javascript@1.26.1': + resolution: {integrity: sha512-CRhA0b8CaSLxS0E9A4Bzcb3LKBNpykfo9F85ozlNyArxjo2NkijtiwrJZ6eHa+NT5I9Kox2IXVdjUsP4dilsmw==} - '@shikijs/engine-oniguruma@1.25.1': - resolution: {integrity: sha512-iKPMh3H+0USHtWfZ1irfMTH6tGmIUFSnqt3E2K8BgI1VEsqiPh0RYkG2WTwzNiM1/WHN4FzYx/nrKR7PDHiRyw==} + '@shikijs/engine-oniguruma@1.26.1': + resolution: {integrity: sha512-F5XuxN1HljLuvfXv7d+mlTkV7XukC1cawdtOo+7pKgPD83CAB1Sf8uHqP3PK0u7njFH0ZhoXE1r+0JzEgAQ+kg==} - '@shikijs/langs@1.25.1': - resolution: {integrity: sha512-hdYjq9aRJplAzGe2qF51PR9IDgEoyGb4IkXvr3Ts6lEdg4Z8M/kdknKRo2EIuv3IR/aKkJXTlBQRM+wr3t20Ew==} + '@shikijs/langs@1.26.1': + resolution: {integrity: sha512-oz/TQiIqZejEIZbGtn68hbJijAOTtYH4TMMSWkWYozwqdpKR3EXgILneQy26WItmJjp3xVspHdiUxUCws4gtuw==} - '@shikijs/themes@1.25.1': - resolution: {integrity: sha512-JO0lDn4LgGqg5QKvgich5ScUmC2okK+LxM9a3iLUH7YMeI2c8UGXThuJv6sZduS7pdJbYQHPrvWq9t/V4GhpbQ==} + '@shikijs/themes@1.26.1': + resolution: {integrity: sha512-JDxVn+z+wgLCiUhBGx2OQrLCkKZQGzNH3nAxFir4PjUcYiyD8Jdms9izyxIogYmSwmoPTatFTdzyrRKbKlSfPA==} - '@shikijs/types@1.25.1': - resolution: {integrity: sha512-dceqFUoO95eY4tpOj3OGq8wE8EgJ4ey6Me1HQEu5UbwIYszFndEll/bjlB8Kp9wl4fx3uM7n4+y9XCYuDBmcXA==} + '@shikijs/types@1.26.1': + resolution: {integrity: sha512-d4B00TKKAMaHuFYgRf3L0gwtvqpW4hVdVwKcZYbBfAAQXspgkbWqnFfuFl3MDH6gLbsubOcr+prcnsqah3ny7Q==} - '@shikijs/vscode-textmate@9.3.1': - resolution: {integrity: sha512-79QfK1393x9Ho60QFyLti+QfdJzRQCVLFb97kOIV7Eo9vQU/roINgk7m24uv0a7AUvN//RDH36FLjjK48v0s9g==} + '@shikijs/vscode-textmate@10.0.1': + resolution: {integrity: sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg==} '@sideway/address@4.1.5': resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} @@ -7974,8 +8035,8 @@ packages: '@types/lodash.isstring@4.0.9': resolution: {integrity: sha512-sjGPpa15VBpMns/4s6Blm567JgxLVVu/eCYCe7h/TdQyPCz9lIhaLSISjN7ZC9cDXmUT2IM/4mNRw8OtYirziw==} - '@types/lodash@4.17.13': - resolution: {integrity: sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==} + '@types/lodash@4.17.14': + resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==} '@types/lru-cache@5.1.1': resolution: {integrity: sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==} @@ -8031,8 +8092,8 @@ packages: '@types/node@20.17.9': resolution: {integrity: sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==} - '@types/node@22.10.4': - resolution: {integrity: sha512-99l6wv4HEzBQhvaU/UGoeBoCK61SCROQaCCGyQSgX2tEQ3rKkNZ2S7CEWnS/4s1LV+8ODdK21UeyR1fHP2mXug==} + '@types/node@22.10.5': + resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} '@types/node@22.7.5': resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} @@ -9141,8 +9202,8 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - bare-events@2.5.0: - resolution: {integrity: sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==} + bare-events@2.5.1: + resolution: {integrity: sha512-Bw2PgKSrZ3uCuSV9WQ998c/GTJTd+9bWj97n7aDQMP8dP/exAZQlJeswPty0ISy+HZD+9Ex+C7CCnc9Q5QJFmQ==} bare-fs@2.3.5: resolution: {integrity: sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==} @@ -11542,8 +11603,8 @@ packages: resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==} engines: {node: '> 0.1.90'} - fast-content-type-parse@2.0.0: - resolution: {integrity: sha512-fCqg/6Sps8tqk8p+kqyKqYfOF0VjPNYrqpLiqNl0RBKmD80B080AJWVV6EkSkscjToNExcXg1+Mfzftrx6+iSA==} + fast-content-type-parse@2.0.1: + resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -12329,8 +12390,8 @@ packages: hast-util-select@4.0.2: resolution: {integrity: sha512-8EEG2//bN5rrzboPWD2HdS3ugLijNioS1pqOTIolXNf67xxShYw4SQEmVXd3imiBG+U2bC2nVTySr/iRAA7Cjg==} - hast-util-to-estree@3.1.0: - resolution: {integrity: sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==} + hast-util-to-estree@3.1.1: + resolution: {integrity: sha512-IWtwwmPskfSmma9RpzCappDUitC8t5jhAynHhc1m2+5trOgsrp7txscUSavc5Ic8PATyAjfrCK1wgtxh2cICVQ==} hast-util-to-html@9.0.4: resolution: {integrity: sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==} @@ -12659,9 +12720,6 @@ packages: inline-source-map@0.6.3: resolution: {integrity: sha512-1aVsPEsJWMJq/pdMU61CDlm1URcW702MTB4w9/zUjMus6H/Py8o7g68Pr9D4I6QluWGt/KdmswuRhaA05xVR1w==} - inline-style-parser@0.1.1: - resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} - inline-style-parser@0.2.4: resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} @@ -14006,8 +14064,8 @@ packages: mdast-util-directive@3.0.0: resolution: {integrity: sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q==} - mdast-util-find-and-replace@3.0.1: - resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} mdast-util-from-markdown@2.0.2: resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} @@ -14991,8 +15049,8 @@ packages: zod: optional: true - openai@4.77.0: - resolution: {integrity: sha512-WWacavtns/7pCUkOWvQIjyOfcdr9X+9n9Vvb0zFeKVDAqwCMDHB+iSr24SVaBAhplvSG6JrRXFpcNM9gWhOGIw==} + openai@4.77.3: + resolution: {integrity: sha512-wLDy4+KWHz31HRFMW2+9KQuVuT2QWhs0z94w1Gm1h2Ut9vIHr9/rHZggbykZEfyiaJRVgw8ZS9K6AylDWzvPYw==} hasBin: true peerDependencies: zod: ^3.23.8 @@ -17309,8 +17367,8 @@ packages: engines: {node: '>=4'} hasBin: true - shiki@1.25.1: - resolution: {integrity: sha512-/1boRvNYwRW3GLG9Y6dXdnZ/Ha+J5T/5y3hV7TGQUcDSBM185D3FCbXlz2eTGNKG2iWCbWqo+P0yhGKZ4/CUrw==} + shiki@1.26.1: + resolution: {integrity: sha512-Gqg6DSTk3wYqaZ5OaYtzjcdxcBvX5kCy24yvRJEgjT5U+WHlmqCThLuBUx0juyxQBi+6ug53IGeuQS07DWwpcw==} shimmer@1.2.1: resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} @@ -17749,9 +17807,6 @@ packages: engines: {node: '>=4'} hasBin: true - style-to-object@0.4.4: - resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} - style-to-object@1.0.8: resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==} @@ -18440,6 +18495,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + engines: {node: '>=14.17'} + hasBin: true + u3@0.1.1: resolution: {integrity: sha512-+J5D5ir763y+Am/QY6hXNRlwljIeRMZMGs0cT6qqZVVzzT3X3nFPXVyPOFRMOR4kupB0T8JnCdpWdp6Q/iXn3w==} @@ -19614,11 +19674,11 @@ snapshots: optionalDependencies: graphql: 16.10.0 - '@0no-co/graphqlsp@1.12.16(graphql@16.10.0)(typescript@5.6.3)': + '@0no-co/graphqlsp@1.12.16(graphql@16.10.0)(typescript@5.7.2)': dependencies: - '@gql.tada/internal': 1.0.8(graphql@16.10.0)(typescript@5.6.3) + '@gql.tada/internal': 1.0.8(graphql@16.10.0)(typescript@5.7.2) graphql: 16.10.0 - typescript: 5.6.3 + typescript: 5.7.2 '@acuminous/bitsyntax@0.1.2': dependencies: @@ -19749,6 +19809,16 @@ snapshots: transitivePeerDependencies: - zod + '@ai-sdk/vue@0.0.59(vue@3.5.13(typescript@5.7.2))(zod@3.23.8)': + dependencies: + '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) + '@ai-sdk/ui-utils': 0.0.50(zod@3.23.8) + swrv: 1.0.4(vue@3.5.13(typescript@5.7.2)) + optionalDependencies: + vue: 3.5.13(typescript@5.7.2) + transitivePeerDependencies: + - zod + '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3)': dependencies: '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3) @@ -20100,7 +20170,7 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-s3@3.721.0': + '@aws-sdk/client-s3@3.722.0': dependencies: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 @@ -20593,7 +20663,7 @@ snapshots: '@smithy/util-middleware': 3.0.11 tslib: 2.8.1 - '@aws-sdk/s3-request-presigner@3.721.0': + '@aws-sdk/s3-request-presigner@3.722.0': dependencies: '@aws-sdk/signature-v4-multi-region': 3.716.0 '@aws-sdk/types': 3.714.0 @@ -21492,13 +21562,13 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@bigmi/core@0.0.4(bitcoinjs-lib@7.0.0-rc.0(typescript@5.6.3))(bs58@6.0.0)(viem@2.21.58(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))': + '@bigmi/core@0.0.4(bitcoinjs-lib@7.0.0-rc.0(typescript@5.7.2))(bs58@6.0.0)(viem@2.21.58(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8))': dependencies: '@noble/hashes': 1.7.0 bech32: 2.0.0 - bitcoinjs-lib: 7.0.0-rc.0(typescript@5.6.3) + bitcoinjs-lib: 7.0.0-rc.0(typescript@5.7.2) bs58: 6.0.0 - viem: 2.21.58(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + viem: 2.21.58(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) '@braintree/sanitize-url@7.1.1': {} @@ -21577,11 +21647,11 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@commitlint/cli@18.6.1(@types/node@22.10.4)(typescript@5.6.3)': + '@commitlint/cli@18.6.1(@types/node@22.10.5)(typescript@5.6.3)': dependencies: '@commitlint/format': 18.6.1 '@commitlint/lint': 18.6.1 - '@commitlint/load': 18.6.1(@types/node@22.10.4)(typescript@5.6.3) + '@commitlint/load': 18.6.1(@types/node@22.10.5)(typescript@5.6.3) '@commitlint/read': 18.6.1 '@commitlint/types': 18.6.1 execa: 5.1.1 @@ -21631,7 +21701,7 @@ snapshots: '@commitlint/rules': 18.6.1 '@commitlint/types': 18.6.1 - '@commitlint/load@18.6.1(@types/node@22.10.4)(typescript@5.6.3)': + '@commitlint/load@18.6.1(@types/node@22.10.5)(typescript@5.6.3)': dependencies: '@commitlint/config-validator': 18.6.1 '@commitlint/execute-rule': 18.6.1 @@ -21639,7 +21709,7 @@ snapshots: '@commitlint/types': 18.6.1 chalk: 4.1.2 cosmiconfig: 8.3.6(typescript@5.6.3) - cosmiconfig-typescript-loader: 5.1.0(@types/node@22.10.4)(cosmiconfig@8.3.6(typescript@5.6.3))(typescript@5.6.3) + cosmiconfig-typescript-loader: 5.1.0(@types/node@22.10.5)(cosmiconfig@8.3.6(typescript@5.6.3))(typescript@5.6.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -22162,7 +22232,7 @@ snapshots: transitivePeerDependencies: - '@algolia/client-search' - '@docusaurus/babel@3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': + '@docusaurus/babel@3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)': dependencies: '@babel/core': 7.26.0 '@babel/generator': 7.26.3 @@ -22175,7 +22245,7 @@ snapshots: '@babel/runtime-corejs3': 7.26.0 '@babel/traverse': 7.26.4 '@docusaurus/logger': 3.6.3 - '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) babel-plugin-dynamic-import-node: 2.3.3 fs-extra: 11.2.0 tslib: 2.8.1 @@ -22190,14 +22260,14 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/bundler@3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': + '@docusaurus/bundler@3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)': dependencies: '@babel/core': 7.26.0 - '@docusaurus/babel': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/babel': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/cssnano-preset': 3.6.3 '@docusaurus/logger': 3.6.3 '@docusaurus/types': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) babel-loader: 9.2.1(@babel/core@7.26.0)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) clean-css: 5.3.3 copy-webpack-plugin: 11.0.0(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) @@ -22209,9 +22279,9 @@ snapshots: mini-css-extract-plugin: 2.9.2(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) null-loader: 4.0.1(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) postcss: 8.4.49 - postcss-loader: 7.3.4(postcss@8.4.49)(typescript@5.6.3)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) + postcss-loader: 7.3.4(postcss@8.4.49)(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) postcss-preset-env: 10.1.3(postcss@8.4.49) - react-dev-utils: 12.0.1(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) + react-dev-utils: 12.0.1(eslint@9.16.0(jiti@2.4.2))(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) terser-webpack-plugin: 5.3.11(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) tslib: 2.8.1 url-loader: 4.1.1(file-loader@6.2.0(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))))(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) @@ -22235,15 +22305,15 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/core@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/core@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/babel': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) - '@docusaurus/bundler': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/babel': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/bundler': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/logger': 3.6.3 - '@docusaurus/mdx-loader': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) - '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/mdx-loader': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/utils-common': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@mdx-js/react': 3.0.1(@types/react@18.3.12)(react@18.3.1) boxen: 6.2.1 chalk: 4.1.2 @@ -22265,7 +22335,7 @@ snapshots: p-map: 4.0.0 prompts: 2.4.2 react: 18.3.1 - react-dev-utils: 12.0.1(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) + react-dev-utils: 12.0.1(eslint@9.16.0(jiti@2.4.2))(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) react-dom: 18.3.1(react@18.3.1) react-helmet-async: 1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.3.1)' @@ -22325,11 +22395,11 @@ snapshots: transitivePeerDependencies: - webpack - '@docusaurus/mdx-loader@3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': + '@docusaurus/mdx-loader@3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)': dependencies: '@docusaurus/logger': 3.6.3 - '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) - '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@mdx-js/mdx': 3.1.0(acorn@8.14.0) '@slorber/remark-comment': 1.0.0 escape-html: 1.0.3 @@ -22381,17 +22451,17 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/plugin-content-blog@3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-content-blog@3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10))(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/logger': 3.6.3 - '@docusaurus/mdx-loader': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) - '@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/mdx-loader': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/types': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/utils-common': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) cheerio: 1.0.0-rc.12 feed: 4.2.2 fs-extra: 11.2.0 @@ -22425,17 +22495,17 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/logger': 3.6.3 - '@docusaurus/mdx-loader': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/mdx-loader': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/module-type-aliases': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/types': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/utils-common': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@types/react-router-config': 5.0.11 combine-promises: 1.2.0 fs-extra: 11.2.0 @@ -22467,13 +22537,13 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-content-pages@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-content-pages@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/mdx-loader': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/mdx-loader': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/types': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) - '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) fs-extra: 11.2.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -22500,11 +22570,11 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-debug@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-debug@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/types': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) fs-extra: 11.2.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -22531,11 +22601,11 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-google-analytics@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-google-analytics@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/types': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) tslib: 2.8.1 @@ -22560,11 +22630,11 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-google-gtag@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-google-gtag@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/types': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@types/gtag.js': 0.0.12 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -22590,11 +22660,11 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-google-tag-manager@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-google-tag-manager@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/types': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) tslib: 2.8.1 @@ -22619,14 +22689,14 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-ideal-image@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-ideal-image@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/lqip-loader': 3.6.3(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) '@docusaurus/responsive-loader': 1.7.0(sharp@0.32.6) '@docusaurus/theme-translations': 3.6.3 '@docusaurus/types': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@slorber/react-ideal-image': 0.0.12(prop-types@15.8.1)(react-waypoint@10.3.0(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -22656,14 +22726,14 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-sitemap@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-sitemap@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/logger': 3.6.3 '@docusaurus/types': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/utils-common': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) fs-extra: 11.2.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -22690,20 +22760,20 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/preset-classic@3.6.3(@algolia/client-search@5.18.0)(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/react@18.3.12)(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.3)(utf-8-validate@5.0.10)': - dependencies: - '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-content-blog': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-content-pages': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-debug': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-google-analytics': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-google-gtag': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-google-tag-manager': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-sitemap': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/theme-classic': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/react@18.3.12)(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) - '@docusaurus/theme-search-algolia': 3.6.3(@algolia/client-search@5.18.0)(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/react@18.3.12)(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/preset-classic@3.6.3(@algolia/client-search@5.18.0)(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/react@18.3.12)(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.7.2)(utf-8-validate@5.0.10)': + dependencies: + '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-blog': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10))(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-pages': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/plugin-debug': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/plugin-google-analytics': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/plugin-google-gtag': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/plugin-google-tag-manager': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/plugin-sitemap': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/theme-classic': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/react@18.3.12)(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/theme-search-algolia': 3.6.3(@algolia/client-search@5.18.0)(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/react@18.3.12)(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/types': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -22742,21 +22812,21 @@ snapshots: optionalDependencies: sharp: 0.32.6 - '@docusaurus/theme-classic@3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/react@18.3.12)(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/theme-classic@3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/react@18.3.12)(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/logger': 3.6.3 - '@docusaurus/mdx-loader': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/mdx-loader': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/module-type-aliases': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-blog': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-content-pages': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/plugin-content-blog': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10))(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-pages': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/theme-translations': 3.6.3 '@docusaurus/types': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/utils-common': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@mdx-js/react': 3.0.1(@types/react@18.3.12)(react@18.3.1) clsx: 2.1.1 copy-text-to-clipboard: 3.2.0 @@ -22793,12 +22863,12 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/theme-common@3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': + '@docusaurus/theme-common@3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)': dependencies: - '@docusaurus/mdx-loader': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/mdx-loader': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/module-type-aliases': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/utils-common': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/history': 4.7.11 '@types/react': 18.3.12 @@ -22819,13 +22889,13 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/theme-mermaid@3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/theme-mermaid@3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10))(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/module-type-aliases': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/types': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) mermaid: 11.4.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -22852,16 +22922,16 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/theme-search-algolia@3.6.3(@algolia/client-search@5.18.0)(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/react@18.3.12)(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/theme-search-algolia@3.6.3(@algolia/client-search@5.18.0)(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/react@18.3.12)(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: '@docsearch/react': 3.8.2(@algolia/client-search@5.18.0)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3) - '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) '@docusaurus/logger': 3.6.3 - '@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/plugin-content-docs': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@docusaurus/theme-common': 3.6.3(@docusaurus/plugin-content-docs@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/theme-translations': 3.6.3 - '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) - '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/utils-validation': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) algoliasearch: 4.24.0 algoliasearch-helper: 3.22.6(algoliasearch@4.24.0) clsx: 2.1.1 @@ -22936,10 +23006,10 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils-validation@3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': + '@docusaurus/utils-validation@3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)': dependencies: '@docusaurus/logger': 3.6.3 - '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/utils': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/utils-common': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) fs-extra: 11.2.0 joi: 17.13.3 @@ -22957,12 +23027,12 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils@3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': + '@docusaurus/utils@3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)': dependencies: '@docusaurus/logger': 3.6.3 '@docusaurus/types': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-common': 3.6.3(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@svgr/webpack': 8.1.0(typescript@5.6.3) + '@svgr/webpack': 8.1.0(typescript@5.7.2) escape-string-regexp: 4.0.0 file-loader: 6.2.0(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) fs-extra: 11.2.0 @@ -23032,7 +23102,7 @@ snapshots: '@anthropic-ai/sdk': 0.30.1(encoding@0.1.13) '@fal-ai/client': 1.2.0 '@types/uuid': 10.0.0 - ai: 3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.16.1))(svelte@5.16.1)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) + ai: 3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.16.1))(svelte@5.16.1)(zod@3.23.8) anthropic-vertex-ai: 1.0.2(encoding@0.1.13)(zod@3.23.8) fastembed: 1.14.1 fastestsmallesttextencoderdecoder: 1.0.22 @@ -23757,23 +23827,23 @@ snapshots: '@floating-ui/utils@0.2.8': {} - '@fuel-ts/abi-coder@0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': + '@fuel-ts/abi-coder@0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': dependencies: - '@fuel-ts/crypto': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/crypto': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/errors': 0.97.2 - '@fuel-ts/hasher': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/hasher': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/interfaces': 0.97.2 '@fuel-ts/math': 0.97.2 - '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) type-fest: 4.31.0 transitivePeerDependencies: - vitest - '@fuel-ts/abi-typegen@0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': + '@fuel-ts/abi-typegen@0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': dependencies: '@fuel-ts/errors': 0.97.2 '@fuel-ts/interfaces': 0.97.2 - '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/versions': 0.97.2 commander: 12.1.0 glob: 10.4.5 @@ -23784,18 +23854,18 @@ snapshots: transitivePeerDependencies: - vitest - '@fuel-ts/account@0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': + '@fuel-ts/account@0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': dependencies: - '@fuel-ts/abi-coder': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/address': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/crypto': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/abi-coder': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/address': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/crypto': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/errors': 0.97.2 - '@fuel-ts/hasher': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/hasher': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/interfaces': 0.97.2 '@fuel-ts/math': 0.97.2 - '@fuel-ts/merkle': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/transactions': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/merkle': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/transactions': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/versions': 0.97.2 '@fuels/vm-asm': 0.58.2 '@noble/curves': 1.8.0 @@ -23808,30 +23878,30 @@ snapshots: - encoding - vitest - '@fuel-ts/address@0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': + '@fuel-ts/address@0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': dependencies: - '@fuel-ts/crypto': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/crypto': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/errors': 0.97.2 '@fuel-ts/interfaces': 0.97.2 - '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@noble/hashes': 1.7.0 bech32: 2.0.0 transitivePeerDependencies: - vitest - '@fuel-ts/contract@0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': + '@fuel-ts/contract@0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': dependencies: - '@fuel-ts/abi-coder': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/account': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/crypto': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/abi-coder': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/account': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/crypto': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/errors': 0.97.2 - '@fuel-ts/hasher': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/hasher': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/interfaces': 0.97.2 '@fuel-ts/math': 0.97.2 - '@fuel-ts/merkle': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/program': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/transactions': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/merkle': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/program': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/transactions': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/versions': 0.97.2 '@fuels/vm-asm': 0.58.2 ramda: 0.30.1 @@ -23839,12 +23909,12 @@ snapshots: - encoding - vitest - '@fuel-ts/crypto@0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': + '@fuel-ts/crypto@0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': dependencies: '@fuel-ts/errors': 0.97.2 '@fuel-ts/interfaces': 0.97.2 '@fuel-ts/math': 0.97.2 - '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@noble/hashes': 1.7.0 transitivePeerDependencies: - vitest @@ -23853,11 +23923,11 @@ snapshots: dependencies: '@fuel-ts/versions': 0.97.2 - '@fuel-ts/hasher@0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': + '@fuel-ts/hasher@0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': dependencies: - '@fuel-ts/crypto': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/crypto': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/interfaces': 0.97.2 - '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@noble/hashes': 1.7.0 transitivePeerDependencies: - vitest @@ -23870,78 +23940,78 @@ snapshots: '@types/bn.js': 5.1.6 bn.js: 5.2.1 - '@fuel-ts/merkle@0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': + '@fuel-ts/merkle@0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': dependencies: - '@fuel-ts/hasher': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/hasher': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/math': 0.97.2 transitivePeerDependencies: - vitest - '@fuel-ts/program@0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': + '@fuel-ts/program@0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': dependencies: - '@fuel-ts/abi-coder': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/account': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/address': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/abi-coder': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/account': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/address': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/errors': 0.97.2 '@fuel-ts/interfaces': 0.97.2 '@fuel-ts/math': 0.97.2 - '@fuel-ts/transactions': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/transactions': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuels/vm-asm': 0.58.2 ramda: 0.30.1 transitivePeerDependencies: - encoding - vitest - '@fuel-ts/recipes@0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': + '@fuel-ts/recipes@0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': dependencies: - '@fuel-ts/abi-coder': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/abi-typegen': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/account': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/contract': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/abi-coder': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/abi-typegen': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/account': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/contract': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/interfaces': 0.97.2 - '@fuel-ts/program': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/transactions': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/program': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/transactions': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) transitivePeerDependencies: - encoding - vitest - '@fuel-ts/script@0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': + '@fuel-ts/script@0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': dependencies: - '@fuel-ts/abi-coder': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/account': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/address': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/abi-coder': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/account': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/address': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/errors': 0.97.2 '@fuel-ts/interfaces': 0.97.2 '@fuel-ts/math': 0.97.2 - '@fuel-ts/program': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/transactions': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/program': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/transactions': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) transitivePeerDependencies: - encoding - vitest - '@fuel-ts/transactions@0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': + '@fuel-ts/transactions@0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': dependencies: - '@fuel-ts/abi-coder': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/address': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/abi-coder': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/address': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/errors': 0.97.2 - '@fuel-ts/hasher': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/hasher': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/interfaces': 0.97.2 '@fuel-ts/math': 0.97.2 - '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) transitivePeerDependencies: - vitest - '@fuel-ts/utils@0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': + '@fuel-ts/utils@0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': dependencies: '@fuel-ts/errors': 0.97.2 '@fuel-ts/interfaces': 0.97.2 '@fuel-ts/math': 0.97.2 '@fuel-ts/versions': 0.97.2 fflate: 0.8.2 - vitest: 2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) + vitest: 2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) '@fuel-ts/versions@0.97.2': dependencies: @@ -23950,10 +24020,10 @@ snapshots: '@fuels/vm-asm@0.58.2': {} - '@goat-sdk/adapter-vercel-ai@0.2.0(@goat-sdk/core@0.4.0)(ai@3.4.33(openai@4.77.0(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.16.1))(svelte@5.16.1)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8))': + '@goat-sdk/adapter-vercel-ai@0.2.0(@goat-sdk/core@0.4.0)(ai@3.4.33(openai@4.77.3(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.16.1))(svelte@5.16.1)(vue@3.5.13(typescript@5.7.2))(zod@3.23.8))': dependencies: '@goat-sdk/core': 0.4.0 - ai: 3.4.33(openai@4.77.0(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.16.1))(svelte@5.16.1)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) + ai: 3.4.33(openai@4.77.3(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.16.1))(svelte@5.16.1)(vue@3.5.13(typescript@5.7.2))(zod@3.23.8) zod: 3.23.8 '@goat-sdk/core@0.4.0': @@ -23961,43 +24031,43 @@ snapshots: reflect-metadata: 0.2.2 zod: 3.23.8 - '@goat-sdk/plugin-erc20@0.2.2(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.21.53(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))': + '@goat-sdk/plugin-erc20@0.2.2(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(viem@2.21.53(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8))': dependencies: '@goat-sdk/core': 0.4.0 - '@goat-sdk/wallet-evm': 0.2.0(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10) - viem: 2.21.53(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + '@goat-sdk/wallet-evm': 0.2.0(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10) + viem: 2.21.53(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) zod: 3.23.8 transitivePeerDependencies: - bufferutil - typescript - utf-8-validate - '@goat-sdk/plugin-kim@0.1.2(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.21.53(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))': + '@goat-sdk/plugin-kim@0.1.2(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(viem@2.21.53(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8))': dependencies: '@goat-sdk/core': 0.4.0 - '@goat-sdk/wallet-evm': 0.2.0(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10) - viem: 2.21.53(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + '@goat-sdk/wallet-evm': 0.2.0(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10) + viem: 2.21.53(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) zod: 3.23.8 transitivePeerDependencies: - bufferutil - typescript - utf-8-validate - '@goat-sdk/wallet-evm@0.2.0(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@goat-sdk/wallet-evm@0.2.0(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: '@goat-sdk/core': 0.4.0 - abitype: 1.0.8(typescript@5.6.3)(zod@3.23.8) - viem: 2.21.49(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + abitype: 1.0.8(typescript@5.7.2)(zod@3.23.8) + viem: 2.21.49(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) zod: 3.23.8 transitivePeerDependencies: - bufferutil - typescript - utf-8-validate - '@goat-sdk/wallet-viem@0.2.0(@goat-sdk/wallet-evm@0.2.0(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10))(viem@2.21.53(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))': + '@goat-sdk/wallet-viem@0.2.0(@goat-sdk/wallet-evm@0.2.0(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10))(viem@2.21.53(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8))': dependencies: - '@goat-sdk/wallet-evm': 0.2.0(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10) - viem: 2.21.53(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + '@goat-sdk/wallet-evm': 0.2.0(@goat-sdk/core@0.4.0)(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10) + viem: 2.21.53(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) '@google-cloud/vertexai@1.9.2(encoding@0.1.13)': dependencies: @@ -24006,18 +24076,18 @@ snapshots: - encoding - supports-color - '@gql.tada/cli-utils@1.6.3(@0no-co/graphqlsp@1.12.16(graphql@16.10.0)(typescript@5.6.3))(graphql@16.10.0)(typescript@5.6.3)': + '@gql.tada/cli-utils@1.6.3(@0no-co/graphqlsp@1.12.16(graphql@16.10.0)(typescript@5.7.2))(graphql@16.10.0)(typescript@5.7.2)': dependencies: - '@0no-co/graphqlsp': 1.12.16(graphql@16.10.0)(typescript@5.6.3) - '@gql.tada/internal': 1.0.8(graphql@16.10.0)(typescript@5.6.3) + '@0no-co/graphqlsp': 1.12.16(graphql@16.10.0)(typescript@5.7.2) + '@gql.tada/internal': 1.0.8(graphql@16.10.0)(typescript@5.7.2) graphql: 16.10.0 - typescript: 5.6.3 + typescript: 5.7.2 - '@gql.tada/internal@1.0.8(graphql@16.10.0)(typescript@5.6.3)': + '@gql.tada/internal@1.0.8(graphql@16.10.0)(typescript@5.7.2)': dependencies: '@0no-co/graphql.web': 1.0.12(graphql@16.10.0) graphql: 16.10.0 - typescript: 5.6.3 + typescript: 5.7.2 '@graphql-typed-document-node/core@3.2.0(graphql@16.10.0)': dependencies: @@ -24192,27 +24262,27 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 20.17.9 + '@types/node': 22.10.5 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3))': + '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.9 + '@types/node': 22.10.5 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3)) + jest-config: 29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -24233,21 +24303,21 @@ snapshots: - supports-color - ts-node - '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3))': + '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.9 + '@types/node': 22.10.5 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) + jest-config: 29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -24275,14 +24345,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.9 + '@types/node': 22.10.5 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)) + jest-config: 29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -24307,7 +24377,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.9 + '@types/node': 22.10.5 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -24325,7 +24395,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.17.9 + '@types/node': 22.10.5 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -24347,7 +24417,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 20.17.9 + '@types/node': 22.10.5 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -24417,7 +24487,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -24481,7 +24551,7 @@ snapshots: dependencies: '@langchain/core': 0.3.27(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)) js-tiktoken: 1.0.15 - openai: 4.77.0(encoding@0.1.13)(zod@3.23.8) + openai: 4.77.3(encoding@0.1.13)(zod@3.23.8) zod: 3.23.8 zod-to-json-schema: 3.24.1(zod@3.23.8) transitivePeerDependencies: @@ -24640,7 +24710,7 @@ snapshots: tslib: 2.8.1 zod: 3.23.8 - '@lerna/create@8.1.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(encoding@0.1.13)(typescript@5.6.3)': + '@lerna/create@8.1.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(encoding@0.1.13)(typescript@5.7.2)': dependencies: '@npmcli/arborist': 7.5.3 '@npmcli/package-json': 5.2.0 @@ -24658,7 +24728,7 @@ snapshots: console-control-strings: 1.1.0 conventional-changelog-core: 5.0.1 conventional-recommended-bump: 7.0.1 - cosmiconfig: 8.3.6(typescript@5.6.3) + cosmiconfig: 8.3.6(typescript@5.7.2) dedent: 1.5.3 execa: 5.0.0 fs-extra: 11.2.0 @@ -24725,18 +24795,18 @@ snapshots: dependencies: '@lifi/types': 16.3.0 - '@lifi/sdk@3.4.1(@solana/wallet-adapter-base@0.9.23(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10)))(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(typescript@5.6.3)(viem@2.21.58(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))': + '@lifi/sdk@3.4.1(@solana/wallet-adapter-base@0.9.23(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10)))(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(typescript@5.7.2)(viem@2.21.58(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8))': dependencies: - '@bigmi/core': 0.0.4(bitcoinjs-lib@7.0.0-rc.0(typescript@5.6.3))(bs58@6.0.0)(viem@2.21.58(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) + '@bigmi/core': 0.0.4(bitcoinjs-lib@7.0.0-rc.0(typescript@5.7.2))(bs58@6.0.0)(viem@2.21.58(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8)) '@lifi/types': 16.3.0 '@noble/curves': 1.8.0 '@noble/hashes': 1.7.0 '@solana/wallet-adapter-base': 0.9.23(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10)) '@solana/web3.js': 1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) bech32: 2.0.0 - bitcoinjs-lib: 7.0.0-rc.0(typescript@5.6.3) + bitcoinjs-lib: 7.0.0-rc.0(typescript@5.7.2) bs58: 6.0.0 - viem: 2.21.58(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + viem: 2.21.58(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) transitivePeerDependencies: - typescript @@ -25177,7 +25247,7 @@ snapshots: dependencies: bs58: 6.0.0 - '@mysten/sui@1.18.0(typescript@5.6.3)': + '@mysten/sui@1.18.0(typescript@5.7.2)': dependencies: '@graphql-typed-document-node/core': 3.2.0(graphql@16.10.0) '@mysten/bcs': 1.2.0 @@ -25188,7 +25258,7 @@ snapshots: '@simplewebauthn/typescript-types': 7.4.0 '@suchipi/femver': 1.0.0 bech32: 2.0.0 - gql.tada: 1.8.10(graphql@16.10.0)(typescript@5.6.3) + gql.tada: 1.8.10(graphql@16.10.0)(typescript@5.7.2) graphql: 16.10.0 jose: 5.9.6 poseidon-lite: 0.2.1 @@ -25332,11 +25402,11 @@ snapshots: transitivePeerDependencies: - encoding - '@neynar/nodejs-sdk@2.7.0(bufferutil@4.0.9)(class-transformer@0.5.1)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)': + '@neynar/nodejs-sdk@2.7.0(bufferutil@4.0.9)(class-transformer@0.5.1)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8)': dependencies: '@openapitools/openapi-generator-cli': 2.15.3(class-transformer@0.5.1)(encoding@0.1.13) semver: 7.6.3 - viem: 2.21.58(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + viem: 2.21.58(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) transitivePeerDependencies: - '@nestjs/microservices' - '@nestjs/platform-express' @@ -25717,9 +25787,9 @@ snapshots: dependencies: '@octokit/auth-app': 7.1.3 '@octokit/auth-unauthenticated': 6.1.0 - '@octokit/core': 6.1.2 + '@octokit/core': 6.1.3 '@octokit/oauth-app': 7.1.4 - '@octokit/plugin-paginate-rest': 11.3.6(@octokit/core@6.1.2) + '@octokit/plugin-paginate-rest': 11.3.6(@octokit/core@6.1.3) '@octokit/types': 13.6.2 '@octokit/webhooks': 13.4.1 @@ -25736,13 +25806,13 @@ snapshots: '@octokit/auth-oauth-app@8.1.1': dependencies: - '@octokit/auth-oauth-device': 7.1.1 + '@octokit/auth-oauth-device': 7.1.2 '@octokit/auth-oauth-user': 5.1.1 '@octokit/request': 9.1.4 '@octokit/types': 13.6.2 universal-user-agent: 7.0.2 - '@octokit/auth-oauth-device@7.1.1': + '@octokit/auth-oauth-device@7.1.2': dependencies: '@octokit/oauth-methods': 5.1.3 '@octokit/request': 9.1.4 @@ -25751,7 +25821,7 @@ snapshots: '@octokit/auth-oauth-user@5.1.1': dependencies: - '@octokit/auth-oauth-device': 7.1.1 + '@octokit/auth-oauth-device': 7.1.2 '@octokit/oauth-methods': 5.1.3 '@octokit/request': 9.1.4 '@octokit/types': 13.6.2 @@ -25790,7 +25860,7 @@ snapshots: before-after-hook: 2.2.3 universal-user-agent: 6.0.1 - '@octokit/core@6.1.2': + '@octokit/core@6.1.3': dependencies: '@octokit/auth-token': 5.1.1 '@octokit/graphql': 8.1.2 @@ -25841,7 +25911,7 @@ snapshots: '@octokit/auth-oauth-app': 8.1.1 '@octokit/auth-oauth-user': 5.1.1 '@octokit/auth-unauthenticated': 6.1.0 - '@octokit/core': 6.1.2 + '@octokit/core': 6.1.3 '@octokit/oauth-authorization-url': 7.1.1 '@octokit/oauth-methods': 5.1.3 '@types/aws-lambda': 8.10.147 @@ -25866,18 +25936,18 @@ snapshots: '@octokit/plugin-enterprise-rest@6.0.1': {} - '@octokit/plugin-paginate-graphql@5.2.4(@octokit/core@6.1.2)': + '@octokit/plugin-paginate-graphql@5.2.4(@octokit/core@6.1.3)': dependencies: - '@octokit/core': 6.1.2 + '@octokit/core': 6.1.3 '@octokit/plugin-paginate-rest@11.3.1(@octokit/core@5.2.0)': dependencies: '@octokit/core': 5.2.0 '@octokit/types': 13.6.2 - '@octokit/plugin-paginate-rest@11.3.6(@octokit/core@6.1.2)': + '@octokit/plugin-paginate-rest@11.3.6(@octokit/core@6.1.3)': dependencies: - '@octokit/core': 6.1.2 + '@octokit/core': 6.1.3 '@octokit/types': 13.6.2 '@octokit/plugin-paginate-rest@6.1.2(@octokit/core@4.2.4(encoding@0.1.13))': @@ -25899,9 +25969,9 @@ snapshots: '@octokit/core': 5.2.0 '@octokit/types': 13.6.2 - '@octokit/plugin-rest-endpoint-methods@13.2.6(@octokit/core@6.1.2)': + '@octokit/plugin-rest-endpoint-methods@13.2.6(@octokit/core@6.1.3)': dependencies: - '@octokit/core': 6.1.2 + '@octokit/core': 6.1.3 '@octokit/types': 13.6.2 '@octokit/plugin-rest-endpoint-methods@7.2.3(@octokit/core@4.2.4(encoding@0.1.13))': @@ -25909,16 +25979,16 @@ snapshots: '@octokit/core': 4.2.4(encoding@0.1.13) '@octokit/types': 10.0.0 - '@octokit/plugin-retry@7.1.2(@octokit/core@6.1.2)': + '@octokit/plugin-retry@7.1.2(@octokit/core@6.1.3)': dependencies: - '@octokit/core': 6.1.2 + '@octokit/core': 6.1.3 '@octokit/request-error': 6.1.6 '@octokit/types': 13.6.2 bottleneck: 2.19.5 - '@octokit/plugin-throttling@9.3.2(@octokit/core@6.1.2)': + '@octokit/plugin-throttling@9.3.2(@octokit/core@6.1.3)': dependencies: - '@octokit/core': 6.1.2 + '@octokit/core': 6.1.3 '@octokit/types': 13.6.2 bottleneck: 2.19.5 @@ -25961,7 +26031,7 @@ snapshots: '@octokit/endpoint': 10.1.2 '@octokit/request-error': 6.1.6 '@octokit/types': 13.6.2 - fast-content-type-parse: 2.0.0 + fast-content-type-parse: 2.0.1 universal-user-agent: 7.0.2 '@octokit/rest@19.0.11(encoding@0.1.13)': @@ -26359,9 +26429,9 @@ snapshots: tslib: 2.8.1 webcrypto-core: 1.8.1 - '@phala/dstack-sdk@0.1.6(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)': + '@phala/dstack-sdk@0.1.6(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8)': optionalDependencies: - viem: 2.21.58(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + viem: 2.21.58(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) transitivePeerDependencies: - bufferutil - typescript @@ -26468,7 +26538,7 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@puppeteer/browsers@0.5.0(typescript@5.6.3)': + '@puppeteer/browsers@0.5.0(typescript@5.7.2)': dependencies: debug: 4.3.4 extract-zip: 2.0.1 @@ -26479,7 +26549,7 @@ snapshots: unbzip2-stream: 1.4.3 yargs: 17.7.1 optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.2 transitivePeerDependencies: - supports-color @@ -26705,10 +26775,10 @@ snapshots: '@radix-ui/rect@1.1.0': {} - '@raydium-io/raydium-sdk-v2@0.1.82-alpha(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@raydium-io/raydium-sdk-v2@0.1.82-alpha(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: '@solana/buffer-layout': 4.0.1 - '@solana/spl-token': 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@solana/spl-token': 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) '@solana/web3.js': 1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) axios: 1.7.9(debug@4.4.0) big.js: 6.2.2 @@ -26736,7 +26806,7 @@ snapshots: '@react-icons/all-files': 4.1.0(react@18.3.1) '@types/big.js': 6.2.2 '@types/bn.js': 5.1.6 - '@types/lodash': 4.17.13 + '@types/lodash': 4.17.14 big.js: 6.2.2 bn.js: 5.2.1 lodash: 4.17.21 @@ -27133,40 +27203,40 @@ snapshots: '@sentry/types': 5.30.0 tslib: 1.14.1 - '@shikijs/core@1.25.1': + '@shikijs/core@1.26.1': dependencies: - '@shikijs/engine-javascript': 1.25.1 - '@shikijs/engine-oniguruma': 1.25.1 - '@shikijs/types': 1.25.1 - '@shikijs/vscode-textmate': 9.3.1 + '@shikijs/engine-javascript': 1.26.1 + '@shikijs/engine-oniguruma': 1.26.1 + '@shikijs/types': 1.26.1 + '@shikijs/vscode-textmate': 10.0.1 '@types/hast': 3.0.4 hast-util-to-html: 9.0.4 - '@shikijs/engine-javascript@1.25.1': + '@shikijs/engine-javascript@1.26.1': dependencies: - '@shikijs/types': 1.25.1 - '@shikijs/vscode-textmate': 9.3.1 + '@shikijs/types': 1.26.1 + '@shikijs/vscode-textmate': 10.0.1 oniguruma-to-es: 0.10.0 - '@shikijs/engine-oniguruma@1.25.1': + '@shikijs/engine-oniguruma@1.26.1': dependencies: - '@shikijs/types': 1.25.1 - '@shikijs/vscode-textmate': 9.3.1 + '@shikijs/types': 1.26.1 + '@shikijs/vscode-textmate': 10.0.1 - '@shikijs/langs@1.25.1': + '@shikijs/langs@1.26.1': dependencies: - '@shikijs/types': 1.25.1 + '@shikijs/types': 1.26.1 - '@shikijs/themes@1.25.1': + '@shikijs/themes@1.26.1': dependencies: - '@shikijs/types': 1.25.1 + '@shikijs/types': 1.26.1 - '@shikijs/types@1.25.1': + '@shikijs/types@1.26.1': dependencies: - '@shikijs/vscode-textmate': 9.3.1 + '@shikijs/vscode-textmate': 10.0.1 '@types/hast': 3.0.4 - '@shikijs/vscode-textmate@9.3.1': {} + '@shikijs/vscode-textmate@10.0.1': {} '@sideway/address@4.1.5': dependencies: @@ -27247,7 +27317,7 @@ snapshots: '@slack/logger@3.0.0': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@slack/types@2.14.0': {} @@ -27256,7 +27326,7 @@ snapshots: '@slack/logger': 3.0.0 '@slack/types': 2.14.0 '@types/is-stream': 1.1.0 - '@types/node': 20.17.9 + '@types/node': 22.10.5 axios: 1.7.9(debug@4.4.0) eventemitter3: 3.1.2 form-data: 2.5.2 @@ -27610,10 +27680,10 @@ snapshots: '@smithy/types': 3.7.2 tslib: 2.8.1 - '@solana-developers/helpers@2.5.6(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@solana-developers/helpers@2.5.6(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: - '@solana/spl-token': 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@solana/spl-token-metadata': 0.1.6(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + '@solana/spl-token': 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@solana/spl-token-metadata': 0.1.6(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) '@solana/web3.js': 1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) bs58: 6.0.0 dotenv: 16.4.7 @@ -27643,10 +27713,10 @@ snapshots: dependencies: '@solana/errors': 2.0.0-preview.2 - '@solana/codecs-core@2.0.0-rc.1(typescript@5.6.3)': + '@solana/codecs-core@2.0.0-rc.1(typescript@5.7.2)': dependencies: - '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) - typescript: 5.6.3 + '@solana/errors': 2.0.0-rc.1(typescript@5.7.2) + typescript: 5.7.2 '@solana/codecs-data-structures@2.0.0-preview.2': dependencies: @@ -27654,23 +27724,23 @@ snapshots: '@solana/codecs-numbers': 2.0.0-preview.2 '@solana/errors': 2.0.0-preview.2 - '@solana/codecs-data-structures@2.0.0-rc.1(typescript@5.6.3)': + '@solana/codecs-data-structures@2.0.0-rc.1(typescript@5.7.2)': dependencies: - '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) - '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.6.3) - '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) - typescript: 5.6.3 + '@solana/codecs-core': 2.0.0-rc.1(typescript@5.7.2) + '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.7.2) + '@solana/errors': 2.0.0-rc.1(typescript@5.7.2) + typescript: 5.7.2 '@solana/codecs-numbers@2.0.0-preview.2': dependencies: '@solana/codecs-core': 2.0.0-preview.2 '@solana/errors': 2.0.0-preview.2 - '@solana/codecs-numbers@2.0.0-rc.1(typescript@5.6.3)': + '@solana/codecs-numbers@2.0.0-rc.1(typescript@5.7.2)': dependencies: - '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) - '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) - typescript: 5.6.3 + '@solana/codecs-core': 2.0.0-rc.1(typescript@5.7.2) + '@solana/errors': 2.0.0-rc.1(typescript@5.7.2) + typescript: 5.7.2 '@solana/codecs-strings@2.0.0-preview.2(fastestsmallesttextencoderdecoder@1.0.22)': dependencies: @@ -27679,13 +27749,13 @@ snapshots: '@solana/errors': 2.0.0-preview.2 fastestsmallesttextencoderdecoder: 1.0.22 - '@solana/codecs-strings@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': + '@solana/codecs-strings@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': dependencies: - '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) - '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.6.3) - '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) + '@solana/codecs-core': 2.0.0-rc.1(typescript@5.7.2) + '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.7.2) + '@solana/errors': 2.0.0-rc.1(typescript@5.7.2) fastestsmallesttextencoderdecoder: 1.0.22 - typescript: 5.6.3 + typescript: 5.7.2 '@solana/codecs@2.0.0-preview.2(fastestsmallesttextencoderdecoder@1.0.22)': dependencies: @@ -27697,14 +27767,14 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/codecs@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': + '@solana/codecs@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': dependencies: - '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) - '@solana/codecs-data-structures': 2.0.0-rc.1(typescript@5.6.3) - '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.6.3) - '@solana/codecs-strings': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) - '@solana/options': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) - typescript: 5.6.3 + '@solana/codecs-core': 2.0.0-rc.1(typescript@5.7.2) + '@solana/codecs-data-structures': 2.0.0-rc.1(typescript@5.7.2) + '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.7.2) + '@solana/codecs-strings': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/options': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + typescript: 5.7.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder @@ -27713,25 +27783,25 @@ snapshots: chalk: 5.4.1 commander: 12.1.0 - '@solana/errors@2.0.0-rc.1(typescript@5.6.3)': + '@solana/errors@2.0.0-rc.1(typescript@5.7.2)': dependencies: chalk: 5.4.1 commander: 12.1.0 - typescript: 5.6.3 + typescript: 5.7.2 '@solana/options@2.0.0-preview.2': dependencies: '@solana/codecs-core': 2.0.0-preview.2 '@solana/codecs-numbers': 2.0.0-preview.2 - '@solana/options@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': + '@solana/options@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': dependencies: - '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) - '@solana/codecs-data-structures': 2.0.0-rc.1(typescript@5.6.3) - '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.6.3) - '@solana/codecs-strings': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) - '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) - typescript: 5.6.3 + '@solana/codecs-core': 2.0.0-rc.1(typescript@5.7.2) + '@solana/codecs-data-structures': 2.0.0-rc.1(typescript@5.7.2) + '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.7.2) + '@solana/codecs-strings': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/errors': 2.0.0-rc.1(typescript@5.7.2) + typescript: 5.7.2 transitivePeerDependencies: - fastestsmallesttextencoderdecoder @@ -27743,28 +27813,28 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/spl-token-group@0.0.7(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': + '@solana/spl-token-group@0.0.7(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': dependencies: - '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) '@solana/web3.js': 1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) transitivePeerDependencies: - fastestsmallesttextencoderdecoder - typescript - '@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': + '@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': dependencies: - '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) '@solana/web3.js': 1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) transitivePeerDependencies: - fastestsmallesttextencoderdecoder - typescript - '@solana/spl-token@0.4.6(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@solana/spl-token@0.4.6(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: '@solana/buffer-layout': 4.0.1 '@solana/buffer-layout-utils': 0.2.0(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) '@solana/spl-token-group': 0.0.4(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22) - '@solana/spl-token-metadata': 0.1.6(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + '@solana/spl-token-metadata': 0.1.6(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) '@solana/web3.js': 1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) buffer: 6.0.3 transitivePeerDependencies: @@ -27774,12 +27844,12 @@ snapshots: - typescript - utf-8-validate - '@solana/spl-token@0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@solana/spl-token@0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: '@solana/buffer-layout': 4.0.1 '@solana/buffer-layout-utils': 0.2.0(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) - '@solana/spl-token-group': 0.0.7(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) - '@solana/spl-token-metadata': 0.1.6(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + '@solana/spl-token-group': 0.0.7(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/spl-token-metadata': 0.1.6(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) '@solana/web3.js': 1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) buffer: 6.0.3 transitivePeerDependencies: @@ -27943,14 +28013,14 @@ snapshots: '@starknet-io/types-js@0.7.10': {} - '@story-protocol/core-sdk@1.2.0-rc.3(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)': + '@story-protocol/core-sdk@1.2.0-rc.3(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8)': dependencies: - abitype: 0.10.3(typescript@5.6.3)(zod@3.23.8) + abitype: 0.10.3(typescript@5.7.2)(zod@3.23.8) axios: 1.7.9(debug@4.4.0) bs58: 6.0.0 dotenv: 16.4.7 multiformats: 9.9.0 - viem: 2.21.58(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + viem: 2.21.58(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) transitivePeerDependencies: - bufferutil - debug @@ -28046,12 +28116,12 @@ snapshots: '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.26.0) '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.26.0) - '@svgr/core@8.1.0(typescript@5.6.3)': + '@svgr/core@8.1.0(typescript@5.7.2)': dependencies: '@babel/core': 7.26.0 '@svgr/babel-preset': 8.1.0(@babel/core@7.26.0) camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@5.6.3) + cosmiconfig: 8.3.6(typescript@5.7.2) snake-case: 3.0.4 transitivePeerDependencies: - supports-color @@ -28062,35 +28132,35 @@ snapshots: '@babel/types': 7.26.3 entities: 4.5.0 - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.6.3))': + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.7.2))': dependencies: '@babel/core': 7.26.0 '@svgr/babel-preset': 8.1.0(@babel/core@7.26.0) - '@svgr/core': 8.1.0(typescript@5.6.3) + '@svgr/core': 8.1.0(typescript@5.7.2) '@svgr/hast-util-to-babel-ast': 8.0.0 svg-parser: 2.0.4 transitivePeerDependencies: - supports-color - '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.6.3))(typescript@5.6.3)': + '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.7.2))(typescript@5.7.2)': dependencies: - '@svgr/core': 8.1.0(typescript@5.6.3) - cosmiconfig: 8.3.6(typescript@5.6.3) + '@svgr/core': 8.1.0(typescript@5.7.2) + cosmiconfig: 8.3.6(typescript@5.7.2) deepmerge: 4.3.1 svgo: 3.3.2 transitivePeerDependencies: - typescript - '@svgr/webpack@8.1.0(typescript@5.6.3)': + '@svgr/webpack@8.1.0(typescript@5.7.2)': dependencies: '@babel/core': 7.26.0 '@babel/plugin-transform-react-constant-elements': 7.25.9(@babel/core@7.26.0) '@babel/preset-env': 7.26.0(@babel/core@7.26.0) '@babel/preset-react': 7.26.3(@babel/core@7.26.0) '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0) - '@svgr/core': 8.1.0(typescript@5.6.3) - '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.6.3)) - '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.6.3))(typescript@5.6.3) + '@svgr/core': 8.1.0(typescript@5.7.2) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.7.2)) + '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.7.2))(typescript@5.7.2) transitivePeerDependencies: - supports-color - typescript @@ -28257,32 +28327,32 @@ snapshots: '@types/better-sqlite3@7.6.12': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/big.js@6.2.2': {} '@types/bn.js@4.11.6': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/bn.js@5.1.6': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/bonjour@3.5.13': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/cacheable-request@6.0.3': dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/responselike': 1.0.3 '@types/chrome@0.0.278': @@ -28293,15 +28363,15 @@ snapshots: '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 5.0.3 - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/connect@3.4.38': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/cors@2.8.17': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/d3-array@3.2.1': {} @@ -28454,14 +28524,14 @@ snapshots: '@types/express-serve-static-core@4.19.6': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/qs': 6.9.17 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 '@types/express-serve-static-core@5.0.3': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/qs': 6.9.17 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -28490,18 +28560,18 @@ snapshots: '@types/fluent-ffmpeg@2.1.27': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/geojson@7946.0.15': {} '@types/glob@8.1.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/gtag.js@0.0.12': {} @@ -28525,7 +28595,7 @@ snapshots: '@types/http-proxy@1.17.15': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/ioredis@5.0.0': dependencies: @@ -28535,7 +28605,7 @@ snapshots: '@types/is-stream@1.1.0': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/istanbul-lib-coverage@2.0.6': {} @@ -28556,17 +28626,17 @@ snapshots: '@types/jsonwebtoken@9.0.7': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/keyv@3.1.4': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/lodash.isstring@4.0.9': dependencies: - '@types/lodash': 4.17.13 + '@types/lodash': 4.17.14 - '@types/lodash@4.17.13': {} + '@types/lodash@4.17.14': {} '@types/lru-cache@5.1.1': {} @@ -28594,12 +28664,12 @@ snapshots: '@types/node-fetch@2.6.12': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 form-data: 4.0.1 '@types/node-forge@1.3.11': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/node@10.17.60': {} @@ -28617,7 +28687,7 @@ snapshots: dependencies: undici-types: 6.19.8 - '@types/node@22.10.4': + '@types/node@22.10.5': dependencies: undici-types: 6.20.0 @@ -28637,7 +28707,7 @@ snapshots: '@types/pbkdf2@3.1.2': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/pdfjs-dist@2.10.378(encoding@0.1.13)': dependencies: @@ -28648,7 +28718,7 @@ snapshots: '@types/pg@8.11.10': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 pg-protocol: 1.7.0 pg-types: 4.0.2 @@ -28692,22 +28762,22 @@ snapshots: '@types/responselike@1.0.3': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/retry@0.12.0': {} '@types/sax@1.2.7': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/secp256k1@4.0.6': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/serve-index@1.9.4': dependencies: @@ -28716,23 +28786,23 @@ snapshots: '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/send': 0.17.4 '@types/sockjs@0.3.36': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/sql.js@1.4.9': dependencies: '@types/emscripten': 1.39.13 - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/stack-utils@2.0.3': {} '@types/tar@6.1.13': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 minipass: 4.2.8 '@types/trusted-types@2.0.7': {} @@ -28751,15 +28821,15 @@ snapshots: '@types/ws@7.4.7': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/ws@8.5.13': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/ws@8.5.3': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 '@types/yargs-parser@21.0.3': {} @@ -28773,7 +28843,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 optional: true '@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3)': @@ -28997,13 +29067,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/eslint-plugin@1.0.1(@typescript-eslint/utils@8.16.0(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3)(vitest@2.1.5(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': + '@vitest/eslint-plugin@1.0.1(@typescript-eslint/utils@8.16.0(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3)(vitest@2.1.5(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0))': dependencies: eslint: 9.16.0(jiti@2.4.2) optionalDependencies: '@typescript-eslint/utils': 8.16.0(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3) typescript: 5.6.3 - vitest: 2.1.5(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) + vitest: 2.1.5(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0) '@vitest/expect@2.1.4': dependencies: @@ -29019,21 +29089,21 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.4(vite@5.4.11(@types/node@22.10.4)(terser@5.37.0))': + '@vitest/mocker@2.1.4(vite@5.4.11(@types/node@22.10.5)(terser@5.37.0))': dependencies: '@vitest/spy': 2.1.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 5.4.11(@types/node@22.10.4)(terser@5.37.0) + vite: 5.4.11(@types/node@22.10.5)(terser@5.37.0) - '@vitest/mocker@2.1.5(vite@5.4.11(@types/node@22.10.4)(terser@5.37.0))': + '@vitest/mocker@2.1.5(vite@5.4.11(@types/node@22.10.5)(terser@5.37.0))': dependencies: '@vitest/spy': 2.1.5 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 5.4.11(@types/node@22.10.4)(terser@5.37.0) + vite: 5.4.11(@types/node@22.10.5)(terser@5.37.0) '@vitest/pretty-format@2.1.4': dependencies: @@ -29143,6 +29213,12 @@ snapshots: '@vue/shared': 3.5.13 vue: 3.5.13(typescript@5.6.3) + '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.7.2))': + dependencies: + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + vue: 3.5.13(typescript@5.7.2) + '@vue/shared@3.5.13': {} '@wallet-standard/base@1.1.0': {} @@ -29612,20 +29688,20 @@ snapshots: fs-extra: 10.1.0 yargs: 17.7.2 - abitype@0.10.3(typescript@5.6.3)(zod@3.23.8): + abitype@0.10.3(typescript@5.7.2)(zod@3.23.8): optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.2 zod: 3.23.8 - abitype@0.7.1(typescript@5.6.3)(zod@3.23.8): + abitype@0.7.1(typescript@5.7.2)(zod@3.23.8): dependencies: - typescript: 5.6.3 + typescript: 5.7.2 optionalDependencies: zod: 3.23.8 - abitype@1.0.6(typescript@5.6.3)(zod@3.23.8): + abitype@1.0.6(typescript@5.7.2)(zod@3.23.8): optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.2 zod: 3.23.8 abitype@1.0.7(typescript@5.6.3)(zod@3.23.8): @@ -29633,11 +29709,21 @@ snapshots: typescript: 5.6.3 zod: 3.23.8 + abitype@1.0.7(typescript@5.7.2)(zod@3.23.8): + optionalDependencies: + typescript: 5.7.2 + zod: 3.23.8 + abitype@1.0.8(typescript@5.6.3)(zod@3.23.8): optionalDependencies: typescript: 5.6.3 zod: 3.23.8 + abitype@1.0.8(typescript@5.7.2)(zod@3.23.8): + optionalDependencies: + typescript: 5.7.2 + zod: 3.23.8 + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 @@ -29756,7 +29842,7 @@ snapshots: - solid-js - vue - ai@3.4.33(openai@4.77.0(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.16.1))(svelte@5.16.1)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8): + ai@3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.16.1))(svelte@5.16.1)(zod@3.23.8): dependencies: '@ai-sdk/provider': 0.0.26 '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) @@ -29764,7 +29850,7 @@ snapshots: '@ai-sdk/solid': 0.0.54(zod@3.23.8) '@ai-sdk/svelte': 0.0.57(svelte@5.16.1)(zod@3.23.8) '@ai-sdk/ui-utils': 0.0.50(zod@3.23.8) - '@ai-sdk/vue': 0.0.59(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) + '@ai-sdk/vue': 0.0.59(vue@3.5.13(typescript@5.7.2))(zod@3.23.8) '@opentelemetry/api': 1.9.0 eventsource-parser: 1.1.2 json-schema: 0.4.0 @@ -29772,7 +29858,32 @@ snapshots: secure-json-parse: 2.7.0 zod-to-json-schema: 3.24.1(zod@3.23.8) optionalDependencies: - openai: 4.77.0(encoding@0.1.13)(zod@3.23.8) + openai: 4.73.0(encoding@0.1.13)(zod@3.23.8) + react: 18.3.1 + sswr: 2.1.0(svelte@5.16.1) + svelte: 5.16.1 + zod: 3.23.8 + transitivePeerDependencies: + - solid-js + - vue + + ai@3.4.33(openai@4.77.3(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.16.1))(svelte@5.16.1)(vue@3.5.13(typescript@5.7.2))(zod@3.23.8): + dependencies: + '@ai-sdk/provider': 0.0.26 + '@ai-sdk/provider-utils': 1.0.22(zod@3.23.8) + '@ai-sdk/react': 0.0.70(react@18.3.1)(zod@3.23.8) + '@ai-sdk/solid': 0.0.54(zod@3.23.8) + '@ai-sdk/svelte': 0.0.57(svelte@5.16.1)(zod@3.23.8) + '@ai-sdk/ui-utils': 0.0.50(zod@3.23.8) + '@ai-sdk/vue': 0.0.59(vue@3.5.13(typescript@5.7.2))(zod@3.23.8) + '@opentelemetry/api': 1.9.0 + eventsource-parser: 1.1.2 + json-schema: 0.4.0 + jsondiffpatch: 0.6.0 + secure-json-parse: 2.7.0 + zod-to-json-schema: 3.24.1(zod@3.23.8) + optionalDependencies: + openai: 4.77.3(encoding@0.1.13)(zod@3.23.8) react: 18.3.1 sswr: 2.1.0(svelte@5.16.1) svelte: 5.16.1 @@ -30285,12 +30396,12 @@ snapshots: balanced-match@1.0.2: {} - bare-events@2.5.0: + bare-events@2.5.1: optional: true bare-fs@2.3.5: dependencies: - bare-events: 2.5.0 + bare-events: 2.5.1 bare-path: 2.1.3 bare-stream: 2.6.1 optional: true @@ -30412,14 +30523,14 @@ snapshots: dependencies: '@noble/hashes': 1.3.0 - bitcoinjs-lib@7.0.0-rc.0(typescript@5.6.3): + bitcoinjs-lib@7.0.0-rc.0(typescript@5.7.2): dependencies: '@noble/hashes': 1.7.0 bech32: 2.0.0 bip174: 3.0.0-rc.1 bs58check: 4.0.0 uint8array-tools: 0.0.9 - valibot: 0.38.0(typescript@5.6.3) + valibot: 0.38.0(typescript@5.7.2) varuint-bitcoin: 2.0.0 transitivePeerDependencies: - typescript @@ -31061,13 +31172,13 @@ snapshots: dependencies: consola: 3.3.3 - cive@0.7.1(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10): + cive@0.7.1(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10): dependencies: '@noble/curves': 1.8.0 '@noble/hashes': 1.7.0 '@scure/bip32': 1.6.1 '@scure/bip39': 1.5.1 - viem: 2.21.58(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + viem: 2.21.58(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) zod: 3.23.8 transitivePeerDependencies: - bufferutil @@ -31516,9 +31627,9 @@ snapshots: dependencies: layout-base: 2.0.1 - cosmiconfig-typescript-loader@5.1.0(@types/node@22.10.4)(cosmiconfig@8.3.6(typescript@5.6.3))(typescript@5.6.3): + cosmiconfig-typescript-loader@5.1.0(@types/node@22.10.5)(cosmiconfig@8.3.6(typescript@5.6.3))(typescript@5.6.3): dependencies: - '@types/node': 22.10.4 + '@types/node': 22.10.5 cosmiconfig: 8.3.6(typescript@5.6.3) jiti: 1.21.7 typescript: 5.6.3 @@ -31547,6 +31658,15 @@ snapshots: optionalDependencies: typescript: 5.6.3 + cosmiconfig@8.3.6(typescript@5.7.2): + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.7.2 + crc-32@1.2.2: {} create-ecdh@4.0.4: @@ -31571,13 +31691,13 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.11 - create-jest@29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3)): + create-jest@29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3)) + jest-config: 29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -31586,13 +31706,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)): + create-jest@29.7.0(@types/node@20.17.9): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) + jest-config: 29.7.0(@types/node@20.17.9) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -31601,13 +31721,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@22.10.4): + create-jest@29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.10.4) + jest-config: 29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -32401,9 +32521,9 @@ snapshots: dependencies: esutils: 2.0.3 - docusaurus-lunr-search@3.5.0(@docusaurus/core@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + docusaurus-lunr-search@3.5.0(@docusaurus/core@3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.6.3(@mdx-js/react@3.0.1(@types/react@18.3.12)(react@18.3.1))(@swc/core@1.10.4(@swc/helpers@0.5.15))(acorn@8.14.0)(bufferutil@4.0.9)(eslint@9.16.0(jiti@2.4.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10) autocomplete.js: 0.37.1 clsx: 1.2.1 gauge: 3.0.2 @@ -32421,9 +32541,9 @@ snapshots: unified: 9.2.2 unist-util-is: 4.1.0 - docusaurus-plugin-typedoc@1.0.5(typedoc-plugin-markdown@4.2.10(typedoc@0.26.11(typescript@5.6.3))): + docusaurus-plugin-typedoc@1.0.5(typedoc-plugin-markdown@4.2.10(typedoc@0.26.11(typescript@5.7.2))): dependencies: - typedoc-plugin-markdown: 4.2.10(typedoc@0.26.11(typescript@5.6.3)) + typedoc-plugin-markdown: 4.2.10(typedoc@0.26.11(typescript@5.7.2)) dom-converter@0.2.0: dependencies: @@ -33287,7 +33407,7 @@ snapshots: eval@0.1.8: dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 require-like: 0.1.2 event-emitter@0.3.5: @@ -33446,7 +33566,7 @@ snapshots: eyes@0.1.8: {} - fast-content-type-parse@2.0.0: {} + fast-content-type-parse@2.0.1: {} fast-deep-equal@3.1.3: {} @@ -33653,11 +33773,11 @@ snapshots: optionalDependencies: debug: 4.4.0(supports-color@8.1.1) - fomo-sdk-solana@1.3.2(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10): + fomo-sdk-solana@1.3.2(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10): dependencies: '@coral-xyz/anchor': 0.30.1(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) - '@raydium-io/raydium-sdk-v2': 0.1.82-alpha(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@solana/spl-token': 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@raydium-io/raydium-sdk-v2': 0.1.82-alpha(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@solana/spl-token': 0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) '@solana/web3.js': 1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) bs58: 6.0.0 coral-xyz3: '@coral-xyz/anchor@0.29.0(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10)' @@ -33688,7 +33808,7 @@ snapshots: forever-agent@0.6.1: {} - fork-ts-checker-webpack-plugin@6.5.3(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))): + fork-ts-checker-webpack-plugin@6.5.3(eslint@9.16.0(jiti@2.4.2))(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))): dependencies: '@babel/code-frame': 7.26.2 '@types/json-schema': 7.0.15 @@ -33703,7 +33823,7 @@ snapshots: schema-utils: 2.7.0 semver: 7.6.3 tapable: 1.1.3 - typescript: 5.6.3 + typescript: 5.7.2 webpack: 5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15)) optionalDependencies: eslint: 9.16.0(jiti@2.4.2) @@ -33801,24 +33921,24 @@ snapshots: fsevents@2.3.3: optional: true - fuels@0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)): + fuels@0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)): dependencies: - '@fuel-ts/abi-coder': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/abi-typegen': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/account': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/address': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/contract': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/crypto': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/abi-coder': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/abi-typegen': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/account': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/address': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/contract': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/crypto': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/errors': 0.97.2 - '@fuel-ts/hasher': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/hasher': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/interfaces': 0.97.2 '@fuel-ts/math': 0.97.2 - '@fuel-ts/merkle': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/program': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/recipes': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/script': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/transactions': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) - '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/merkle': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/program': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/recipes': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/script': 0.97.2(encoding@0.1.13)(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/transactions': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) + '@fuel-ts/utils': 0.97.2(vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0)) '@fuel-ts/versions': 0.97.2 bundle-require: 5.1.0(esbuild@0.24.2) chalk: 4.1.2 @@ -34219,13 +34339,13 @@ snapshots: p-cancelable: 3.0.0 responselike: 3.0.0 - gql.tada@1.8.10(graphql@16.10.0)(typescript@5.6.3): + gql.tada@1.8.10(graphql@16.10.0)(typescript@5.7.2): dependencies: '@0no-co/graphql.web': 1.0.12(graphql@16.10.0) - '@0no-co/graphqlsp': 1.12.16(graphql@16.10.0)(typescript@5.6.3) - '@gql.tada/cli-utils': 1.6.3(@0no-co/graphqlsp@1.12.16(graphql@16.10.0)(typescript@5.6.3))(graphql@16.10.0)(typescript@5.6.3) - '@gql.tada/internal': 1.0.8(graphql@16.10.0)(typescript@5.6.3) - typescript: 5.6.3 + '@0no-co/graphqlsp': 1.12.16(graphql@16.10.0)(typescript@5.7.2) + '@gql.tada/cli-utils': 1.6.3(@0no-co/graphqlsp@1.12.16(graphql@16.10.0)(typescript@5.7.2))(graphql@16.10.0)(typescript@5.7.2) + '@gql.tada/internal': 1.0.8(graphql@16.10.0)(typescript@5.7.2) + typescript: 5.7.2 transitivePeerDependencies: - '@gql.tada/svelte-support' - '@gql.tada/vue-support' @@ -34310,7 +34430,7 @@ snapshots: hard-rejection@2.1.0: {} - hardhat@2.22.17(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.4)(typescript@5.6.3))(typescript@5.6.3)(utf-8-validate@5.0.10): + hardhat@2.22.17(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2))(typescript@5.7.2)(utf-8-validate@5.0.10): dependencies: '@ethersproject/abi': 5.7.0 '@metamask/eth-sig-util': 4.0.1 @@ -34357,8 +34477,8 @@ snapshots: uuid: 8.3.2 ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10) optionalDependencies: - ts-node: 10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.4)(typescript@5.6.3) - typescript: 5.6.3 + ts-node: 10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2) + typescript: 5.7.2 transitivePeerDependencies: - bufferutil - c-kzg @@ -34476,7 +34596,7 @@ snapshots: unist-util-visit: 2.0.3 zwitch: 1.0.5 - hast-util-to-estree@3.1.0: + hast-util-to-estree@3.1.1: dependencies: '@types/estree': 1.0.6 '@types/estree-jsx': 1.0.5 @@ -34491,7 +34611,7 @@ snapshots: mdast-util-mdxjs-esm: 2.0.1 property-information: 6.5.0 space-separated-tokens: 2.0.2 - style-to-object: 0.4.4 + style-to-object: 1.0.8 unist-util-position: 5.0.0 zwitch: 2.0.4 transitivePeerDependencies: @@ -34901,8 +35021,6 @@ snapshots: dependencies: source-map: 0.5.7 - inline-style-parser@0.1.1: {} - inline-style-parser@0.2.4: {} inquirer@8.2.6: @@ -35427,7 +35545,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.9 + '@types/node': 22.10.5 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.3 @@ -35447,16 +35565,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3)): + jest-cli@29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3)) + create-jest: 29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3)) + jest-config: 29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -35466,16 +35584,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)): + jest-cli@29.7.0(@types/node@20.17.9): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) + create-jest: 29.7.0(@types/node@20.17.9) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) + jest-config: 29.7.0(@types/node@20.17.9) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -35485,16 +35603,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@22.10.4): + jest-cli@29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.10.4) + create-jest: 29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@22.10.4) + jest-config: 29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -35523,7 +35641,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3)): + jest-config@29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2)): dependencies: '@babel/core': 7.26.0 '@jest/test-sequencer': 29.7.0 @@ -35549,12 +35667,12 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 18.19.69 - ts-node: 10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3) + ts-node: 10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3)): + jest-config@29.7.0(@types/node@20.17.9): dependencies: '@babel/core': 7.26.0 '@jest/test-sequencer': 29.7.0 @@ -35580,12 +35698,11 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.17.9 - ts-node: 10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)): + jest-config@29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2)): dependencies: '@babel/core': 7.26.0 '@jest/test-sequencer': 29.7.0 @@ -35610,13 +35727,13 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.17.9 - ts-node: 10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3) + '@types/node': 22.10.5 + ts-node: 10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)): + jest-config@29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)): dependencies: '@babel/core': 7.26.0 '@jest/test-sequencer': 29.7.0 @@ -35641,13 +35758,13 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.17.9 - ts-node: 10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3) + '@types/node': 22.10.5 + ts-node: 10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@22.10.4): + jest-config@29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3)): dependencies: '@babel/core': 7.26.0 '@jest/test-sequencer': 29.7.0 @@ -35672,7 +35789,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 22.10.4 + '@types/node': 22.10.5 + ts-node: 10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -35732,7 +35850,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.9 + '@types/node': 22.10.5 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -35742,7 +35860,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.17.9 + '@types/node': 22.10.5 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -35781,7 +35899,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.17.9 + '@types/node': 22.10.5 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -35816,7 +35934,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.9 + '@types/node': 22.10.5 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -35844,7 +35962,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.9 + '@types/node': 22.10.5 chalk: 4.1.2 cjs-module-lexer: 1.4.1 collect-v8-coverage: 1.0.2 @@ -35890,7 +36008,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.17.9 + '@types/node': 22.10.5 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -35909,7 +36027,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.9 + '@types/node': 22.10.5 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -35918,47 +36036,47 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 20.17.9 + '@types/node': 22.10.5 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3)): + jest@29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3)) + jest-cli: 29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node - jest@29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)): + jest@29.7.0(@types/node@20.17.9): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) + jest-cli: 29.7.0(@types/node@20.17.9) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node - jest@29.7.0(@types/node@22.10.4): + jest@29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.10.4) + jest-cli: 29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -36311,7 +36429,7 @@ snapshots: lerna@8.1.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(encoding@0.1.13): dependencies: - '@lerna/create': 8.1.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(encoding@0.1.13)(typescript@5.6.3) + '@lerna/create': 8.1.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(encoding@0.1.13)(typescript@5.7.2) '@npmcli/arborist': 7.5.3 '@npmcli/package-json': 5.2.0 '@npmcli/run-script': 8.1.0 @@ -36329,7 +36447,7 @@ snapshots: conventional-changelog-angular: 7.0.0 conventional-changelog-core: 5.0.1 conventional-recommended-bump: 7.0.1 - cosmiconfig: 8.3.6(typescript@5.6.3) + cosmiconfig: 8.3.6(typescript@5.7.2) dedent: 1.5.3 envinfo: 7.13.0 execa: 5.0.0 @@ -36380,7 +36498,7 @@ snapshots: strong-log-transformer: 2.1.0 tar: 6.2.1 temp-dir: 1.0.0 - typescript: 5.6.3 + typescript: 5.7.2 upath: 2.0.1 uuid: 10.0.0 validate-npm-package-license: 3.0.4 @@ -36802,7 +36920,7 @@ snapshots: transitivePeerDependencies: - supports-color - mdast-util-find-and-replace@3.0.1: + mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 escape-string-regexp: 5.0.0 @@ -36842,7 +36960,7 @@ snapshots: '@types/mdast': 4.0.4 ccount: 2.0.1 devlop: 1.1.0 - mdast-util-find-and-replace: 3.0.1 + mdast-util-find-and-replace: 3.0.2 micromark-util-character: 2.1.1 mdast-util-gfm-footnote@2.0.0: @@ -37545,7 +37663,7 @@ snapshots: mkdirp@3.0.1: {} - mkdist@1.6.0(typescript@5.6.3): + mkdist@1.6.0(typescript@5.7.2): dependencies: autoprefixer: 10.4.20(postcss@8.4.49) citty: 0.1.6 @@ -37561,7 +37679,7 @@ snapshots: semver: 7.6.3 tinyglobby: 0.2.10 optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.2 mlly@1.7.3: dependencies: @@ -37909,7 +38027,7 @@ snapshots: process: 0.11.10 uuid: 9.0.1 - node-llama-cpp@3.1.1(typescript@5.6.3): + node-llama-cpp@3.1.1(typescript@5.7.2): dependencies: '@huggingface/jinja': 0.3.2 async-retry: 1.3.3 @@ -37953,7 +38071,7 @@ snapshots: '@node-llama-cpp/win-x64': 3.1.1 '@node-llama-cpp/win-x64-cuda': 3.1.1 '@node-llama-cpp/win-x64-vulkan': 3.1.1 - typescript: 5.6.3 + typescript: 5.7.2 transitivePeerDependencies: - supports-color @@ -38193,13 +38311,13 @@ snapshots: octokit@4.0.3: dependencies: '@octokit/app': 15.1.1 - '@octokit/core': 6.1.2 + '@octokit/core': 6.1.3 '@octokit/oauth-app': 7.1.4 - '@octokit/plugin-paginate-graphql': 5.2.4(@octokit/core@6.1.2) - '@octokit/plugin-paginate-rest': 11.3.6(@octokit/core@6.1.2) - '@octokit/plugin-rest-endpoint-methods': 13.2.6(@octokit/core@6.1.2) - '@octokit/plugin-retry': 7.1.2(@octokit/core@6.1.2) - '@octokit/plugin-throttling': 9.3.2(@octokit/core@6.1.2) + '@octokit/plugin-paginate-graphql': 5.2.4(@octokit/core@6.1.3) + '@octokit/plugin-paginate-rest': 11.3.6(@octokit/core@6.1.3) + '@octokit/plugin-rest-endpoint-methods': 13.2.6(@octokit/core@6.1.3) + '@octokit/plugin-retry': 7.1.2(@octokit/core@6.1.3) + '@octokit/plugin-throttling': 9.3.2(@octokit/core@6.1.3) '@octokit/request-error': 6.1.6 '@octokit/types': 13.6.2 @@ -38305,7 +38423,7 @@ snapshots: transitivePeerDependencies: - encoding - openai@4.77.0(encoding@0.1.13)(zod@3.23.8): + openai@4.77.3(encoding@0.1.13)(zod@3.23.8): dependencies: '@types/node': 18.19.69 '@types/node-fetch': 2.6.12 @@ -38383,17 +38501,17 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 - ox@0.1.2(typescript@5.6.3)(zod@3.23.8): + ox@0.1.2(typescript@5.7.2)(zod@3.23.8): dependencies: '@adraffy/ens-normalize': 1.11.0 '@noble/curves': 1.6.0 '@noble/hashes': 1.5.0 '@scure/bip32': 1.5.0 '@scure/bip39': 1.4.0 - abitype: 1.0.6(typescript@5.6.3)(zod@3.23.8) + abitype: 1.0.6(typescript@5.7.2)(zod@3.23.8) eventemitter3: 5.0.1 optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.2 transitivePeerDependencies: - zod @@ -38411,6 +38529,20 @@ snapshots: transitivePeerDependencies: - zod + ox@0.4.4(typescript@5.7.2)(zod@3.23.8): + dependencies: + '@adraffy/ens-normalize': 1.11.0 + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.1 + '@scure/bip32': 1.6.0 + '@scure/bip39': 1.5.0 + abitype: 1.0.7(typescript@5.7.2)(zod@3.23.8) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 5.7.2 + transitivePeerDependencies: + - zod + p-cancelable@2.1.1: {} p-cancelable@3.0.0: {} @@ -39190,9 +39322,9 @@ snapshots: tsx: 4.19.2 yaml: 2.7.0 - postcss-loader@7.3.4(postcss@8.4.49)(typescript@5.6.3)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))): + postcss-loader@7.3.4(postcss@8.4.49)(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))): dependencies: - cosmiconfig: 8.3.6(typescript@5.6.3) + cosmiconfig: 8.3.6(typescript@5.7.2) jiti: 1.21.7 postcss: 8.4.49 semver: 7.6.3 @@ -39759,7 +39891,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 20.17.9 + '@types/node': 22.10.5 long: 5.2.3 protocols@2.0.1: {} @@ -39819,11 +39951,11 @@ snapshots: end-of-stream: 1.4.4 once: 1.4.0 - pumpdotfun-sdk@1.3.2(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(rollup@4.29.1)(typescript@5.6.3)(utf-8-validate@5.0.10): + pumpdotfun-sdk@1.3.2(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(rollup@4.29.1)(typescript@5.7.2)(utf-8-validate@5.0.10): dependencies: '@coral-xyz/anchor': 0.30.1(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) '@rollup/plugin-json': 6.1.0(rollup@4.29.1) - '@solana/spl-token': 0.4.6(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@solana/spl-token': 0.4.6(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) '@solana/web3.js': 1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil @@ -39843,9 +39975,9 @@ snapshots: dependencies: escape-goat: 4.0.0 - puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10): + puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10): dependencies: - '@puppeteer/browsers': 0.5.0(typescript@5.6.3) + '@puppeteer/browsers': 0.5.0(typescript@5.7.2) chromium-bidi: 0.4.7(devtools-protocol@0.0.1107588) cross-fetch: 3.1.5(encoding@0.1.13) debug: 4.3.4 @@ -39857,20 +39989,20 @@ snapshots: unbzip2-stream: 1.4.3 ws: 8.13.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.2 transitivePeerDependencies: - bufferutil - encoding - supports-color - utf-8-validate - puppeteer-extra-plugin-capsolver@2.0.1(bufferutil@4.0.9)(encoding@0.1.13)(puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10))(typescript@5.6.3)(utf-8-validate@5.0.10): + puppeteer-extra-plugin-capsolver@2.0.1(bufferutil@4.0.9)(encoding@0.1.13)(puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10))(typescript@5.7.2)(utf-8-validate@5.0.10): dependencies: axios: 1.7.9(debug@4.4.0) capsolver-npm: 2.0.2 - puppeteer: 19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10) - puppeteer-extra: 3.3.6(puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10))(puppeteer@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)) - puppeteer-extra-plugin: 3.2.3(puppeteer-extra@3.3.6(puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10))(puppeteer@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10))) + puppeteer: 19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10) + puppeteer-extra: 3.3.6(puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10))(puppeteer@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)) + puppeteer-extra-plugin: 3.2.3(puppeteer-extra@3.3.6(puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10))(puppeteer@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10))) transitivePeerDependencies: - '@types/puppeteer' - bufferutil @@ -39882,35 +40014,35 @@ snapshots: - typescript - utf-8-validate - puppeteer-extra-plugin@3.2.3(puppeteer-extra@3.3.6(puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10))(puppeteer@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10))): + puppeteer-extra-plugin@3.2.3(puppeteer-extra@3.3.6(puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10))(puppeteer@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10))): dependencies: '@types/debug': 4.1.12 debug: 4.4.0(supports-color@8.1.1) merge-deep: 3.0.3 optionalDependencies: - puppeteer-extra: 3.3.6(puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10))(puppeteer@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)) + puppeteer-extra: 3.3.6(puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10))(puppeteer@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)) transitivePeerDependencies: - supports-color - puppeteer-extra@3.3.6(puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10))(puppeteer@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)): + puppeteer-extra@3.3.6(puppeteer-core@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10))(puppeteer@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)): dependencies: '@types/debug': 4.1.12 debug: 4.4.0(supports-color@8.1.1) deepmerge: 4.3.1 optionalDependencies: - puppeteer: 19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10) - puppeteer-core: 19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10) + puppeteer: 19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10) + puppeteer-core: 19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10) transitivePeerDependencies: - supports-color - puppeteer@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10): + puppeteer@19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10): dependencies: - '@puppeteer/browsers': 0.5.0(typescript@5.6.3) + '@puppeteer/browsers': 0.5.0(typescript@5.7.2) cosmiconfig: 8.1.3 https-proxy-agent: 5.0.1 progress: 2.0.3 proxy-from-env: 1.1.0 - puppeteer-core: 19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10) + puppeteer-core: 19.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - encoding @@ -40004,7 +40136,7 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-dev-utils@12.0.1(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))): + react-dev-utils@12.0.1(eslint@9.16.0(jiti@2.4.2))(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))): dependencies: '@babel/code-frame': 7.26.2 address: 1.2.2 @@ -40015,7 +40147,7 @@ snapshots: escape-string-regexp: 4.0.0 filesize: 8.0.7 find-up: 5.0.0 - fork-ts-checker-webpack-plugin: 6.5.3(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) + fork-ts-checker-webpack-plugin: 6.5.3(eslint@9.16.0(jiti@2.4.2))(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))) global-modules: 2.0.0 globby: 11.1.0 gzip-size: 6.0.0 @@ -40032,7 +40164,7 @@ snapshots: text-table: 0.2.0 webpack: 5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15)) optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.2 transitivePeerDependencies: - eslint - supports-color @@ -40413,7 +40545,7 @@ snapshots: dependencies: '@types/estree': 1.0.6 '@types/hast': 3.0.4 - hast-util-to-estree: 3.1.0 + hast-util-to-estree: 3.1.1 transitivePeerDependencies: - supports-color @@ -40432,7 +40564,7 @@ snapshots: dependencies: '@types/mdast': 4.0.4 emoticon: 4.1.0 - mdast-util-find-and-replace: 3.0.1 + mdast-util-find-and-replace: 3.0.2 node-emoji: 2.2.0 unified: 11.0.5 @@ -40623,11 +40755,11 @@ snapshots: robust-predicates@3.0.2: {} - rollup-plugin-dts@6.1.1(rollup@3.29.5)(typescript@5.6.3): + rollup-plugin-dts@6.1.1(rollup@3.29.5)(typescript@5.7.2): dependencies: magic-string: 0.30.17 rollup: 3.29.5 - typescript: 5.6.3 + typescript: 5.7.2 optionalDependencies: '@babel/code-frame': 7.26.2 @@ -41021,15 +41153,15 @@ snapshots: interpret: 1.4.0 rechoir: 0.6.2 - shiki@1.25.1: + shiki@1.26.1: dependencies: - '@shikijs/core': 1.25.1 - '@shikijs/engine-javascript': 1.25.1 - '@shikijs/engine-oniguruma': 1.25.1 - '@shikijs/langs': 1.25.1 - '@shikijs/themes': 1.25.1 - '@shikijs/types': 1.25.1 - '@shikijs/vscode-textmate': 9.3.1 + '@shikijs/core': 1.26.1 + '@shikijs/engine-javascript': 1.26.1 + '@shikijs/engine-oniguruma': 1.26.1 + '@shikijs/langs': 1.26.1 + '@shikijs/themes': 1.26.1 + '@shikijs/types': 1.26.1 + '@shikijs/vscode-textmate': 10.0.1 '@types/hast': 3.0.4 shimmer@1.2.1: {} @@ -41422,7 +41554,7 @@ snapshots: queue-tick: 1.0.1 text-decoder: 1.2.3 optionalDependencies: - bare-events: 2.5.0 + bare-events: 2.5.1 strict-uri-encode@2.0.0: {} @@ -41541,10 +41673,6 @@ snapshots: minimist: 1.2.8 through: 2.3.8 - style-to-object@0.4.4: - dependencies: - inline-style-parser: 0.1.1 - style-to-object@1.0.8: dependencies: inline-style-parser: 0.2.4 @@ -41645,6 +41773,10 @@ snapshots: dependencies: vue: 3.5.13(typescript@5.6.3) + swrv@1.0.4(vue@3.5.13(typescript@5.7.2)): + dependencies: + vue: 3.5.13(typescript@5.7.2) + symbol-tree@3.2.4: {} symbol.inspect@1.0.1: {} @@ -41981,18 +42113,18 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.24.2)(jest@29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)))(typescript@5.6.3): + ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.24.2)(jest@29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)))(typescript@5.7.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.17.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3)) + jest: 29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.6.3 - typescript: 5.6.3 + typescript: 5.7.2 yargs-parser: 21.1.1 optionalDependencies: '@babel/core': 7.26.0 @@ -42001,12 +42133,31 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.26.0) esbuild: 0.24.2 - ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3)))(typescript@5.6.3): + ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2)))(typescript@5.7.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3)) + jest: 29.7.0(@types/node@18.19.69)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2)) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.6.3 + typescript: 5.7.2 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.26.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.0) + + ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.9))(typescript@5.6.3): + dependencies: + bs-logger: 0.2.6 + ejs: 3.1.10 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@20.17.9) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -42041,7 +42192,7 @@ snapshots: ts-mixer@6.0.4: {} - ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.6.3): + ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@18.19.69)(typescript@5.7.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -42055,52 +42206,31 @@ snapshots: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.6.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - optionalDependencies: - '@swc/core': 1.10.4(@swc/helpers@0.5.15) - - ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@20.17.9)(typescript@5.6.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.11 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 20.17.9 - acorn: 8.14.0 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.6.3 + typescript: 5.7.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: '@swc/core': 1.10.4(@swc/helpers@0.5.15) - ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.4)(typescript@5.6.3): + ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.10.4 + '@types/node': 22.10.5 acorn: 8.14.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.6.3 + typescript: 5.7.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: '@swc/core': 1.10.4(@swc/helpers@0.5.15) - optional: true ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.8.4)(typescript@5.6.3): dependencies: @@ -42170,6 +42300,34 @@ snapshots: - tsx - yaml + tsup@8.3.5(@swc/core@1.10.4(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.7.0): + dependencies: + bundle-require: 5.1.0(esbuild@0.24.2) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.3.3 + debug: 4.4.0(supports-color@8.1.1) + esbuild: 0.24.2 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(jiti@2.4.2)(postcss@8.4.49)(tsx@4.19.2)(yaml@2.7.0) + resolve-from: 5.0.0 + rollup: 4.29.1 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.10 + tree-kill: 1.2.2 + optionalDependencies: + '@swc/core': 1.10.4(@swc/helpers@0.5.15) + postcss: 8.4.49 + typescript: 5.7.2 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + tsx@4.19.2: dependencies: esbuild: 0.23.1 @@ -42320,19 +42478,28 @@ snapshots: typedarray@0.0.6: {} - typedoc-plugin-markdown@4.2.10(typedoc@0.26.11(typescript@5.6.3)): + typedoc-plugin-markdown@4.2.10(typedoc@0.26.11(typescript@5.7.2)): dependencies: - typedoc: 0.26.11(typescript@5.6.3) + typedoc: 0.26.11(typescript@5.7.2) typedoc@0.26.11(typescript@5.6.3): dependencies: lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 - shiki: 1.25.1 + shiki: 1.26.1 typescript: 5.6.3 yaml: 2.7.0 + typedoc@0.26.11(typescript@5.7.2): + dependencies: + lunr: 2.3.9 + markdown-it: 14.1.0 + minimatch: 9.0.5 + shiki: 1.26.1 + typescript: 5.7.2 + yaml: 2.7.0 + typeforce@1.18.0: {} typescript-eslint@8.11.0(eslint@9.16.0(jiti@2.4.2))(typescript@5.6.3): @@ -42348,6 +42515,8 @@ snapshots: typescript@5.6.3: {} + typescript@5.7.2: {} + u3@0.1.1: {} uc.micro@2.1.0: {} @@ -42377,7 +42546,7 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - unbuild@2.0.0(typescript@5.6.3): + unbuild@2.0.0(typescript@5.7.2): dependencies: '@rollup/plugin-alias': 5.1.1(rollup@3.29.5) '@rollup/plugin-commonjs': 25.0.8(rollup@3.29.5) @@ -42394,17 +42563,17 @@ snapshots: hookable: 5.5.3 jiti: 1.21.7 magic-string: 0.30.17 - mkdist: 1.6.0(typescript@5.6.3) + mkdist: 1.6.0(typescript@5.7.2) mlly: 1.7.3 pathe: 1.1.2 pkg-types: 1.3.0 pretty-bytes: 6.1.1 rollup: 3.29.5 - rollup-plugin-dts: 6.1.1(rollup@3.29.5)(typescript@5.6.3) + rollup-plugin-dts: 6.1.1(rollup@3.29.5)(typescript@5.7.2) scule: 1.3.0 untyped: 1.5.2 optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.2 transitivePeerDependencies: - sass - supports-color @@ -42723,9 +42892,9 @@ snapshots: valibot@0.36.0: {} - valibot@0.38.0(typescript@5.6.3): + valibot@0.38.0(typescript@5.7.2): optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.2 valid-url@1.0.9: {} @@ -42789,37 +42958,37 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - viem@2.21.49(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8): + viem@2.21.49(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8): dependencies: '@noble/curves': 1.6.0 '@noble/hashes': 1.5.0 '@scure/bip32': 1.5.0 '@scure/bip39': 1.4.0 - abitype: 1.0.6(typescript@5.6.3)(zod@3.23.8) + abitype: 1.0.6(typescript@5.7.2)(zod@3.23.8) isows: 1.0.6(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - ox: 0.1.2(typescript@5.6.3)(zod@3.23.8) + ox: 0.1.2(typescript@5.7.2)(zod@3.23.8) webauthn-p256: 0.0.10 ws: 8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.2 transitivePeerDependencies: - bufferutil - utf-8-validate - zod - viem@2.21.53(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8): + viem@2.21.53(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8): dependencies: '@noble/curves': 1.6.0 '@noble/hashes': 1.5.0 '@scure/bip32': 1.5.0 '@scure/bip39': 1.4.0 - abitype: 1.0.6(typescript@5.6.3)(zod@3.23.8) + abitype: 1.0.6(typescript@5.7.2)(zod@3.23.8) isows: 1.0.6(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - ox: 0.1.2(typescript@5.6.3)(zod@3.23.8) + ox: 0.1.2(typescript@5.7.2)(zod@3.23.8) webauthn-p256: 0.0.10 ws: 8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.2 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -42843,12 +43012,30 @@ snapshots: - utf-8-validate - zod - vite-node@2.1.4(@types/node@22.10.4)(terser@5.37.0): + viem@2.21.58(bufferutil@4.0.9)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8): + dependencies: + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.1 + '@scure/bip32': 1.6.0 + '@scure/bip39': 1.5.0 + abitype: 1.0.7(typescript@5.7.2)(zod@3.23.8) + isows: 1.0.6(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + ox: 0.4.4(typescript@5.7.2)(zod@3.23.8) + webauthn-p256: 0.0.10 + ws: 8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + optionalDependencies: + typescript: 5.7.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + + vite-node@2.1.4(@types/node@22.10.5)(terser@5.37.0): dependencies: cac: 6.7.14 debug: 4.4.0(supports-color@8.1.1) pathe: 1.1.2 - vite: 5.4.11(@types/node@22.10.4)(terser@5.37.0) + vite: 5.4.11(@types/node@22.10.5)(terser@5.37.0) transitivePeerDependencies: - '@types/node' - less @@ -42860,13 +43047,13 @@ snapshots: - supports-color - terser - vite-node@2.1.5(@types/node@22.10.4)(terser@5.37.0): + vite-node@2.1.5(@types/node@22.10.5)(terser@5.37.0): dependencies: cac: 6.7.14 debug: 4.4.0(supports-color@8.1.1) es-module-lexer: 1.6.0 pathe: 1.1.2 - vite: 5.4.11(@types/node@22.10.4)(terser@5.37.0) + vite: 5.4.11(@types/node@22.10.5)(terser@5.37.0) transitivePeerDependencies: - '@types/node' - less @@ -42910,13 +43097,13 @@ snapshots: dependencies: vite: link:client/@tanstack/router-plugin/vite - vite@5.4.11(@types/node@22.10.4)(terser@5.37.0): + vite@5.4.11(@types/node@22.10.5)(terser@5.37.0): dependencies: esbuild: 0.21.5 postcss: 8.4.49 rollup: 4.29.1 optionalDependencies: - '@types/node': 22.10.4 + '@types/node': 22.10.5 fsevents: 2.3.3 terser: 5.37.0 @@ -42930,10 +43117,10 @@ snapshots: fsevents: 2.3.3 terser: 5.37.0 - vitest@2.1.4(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0): + vitest@2.1.4(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0): dependencies: '@vitest/expect': 2.1.4 - '@vitest/mocker': 2.1.4(vite@5.4.11(@types/node@22.10.4)(terser@5.37.0)) + '@vitest/mocker': 2.1.4(vite@5.4.11(@types/node@22.10.5)(terser@5.37.0)) '@vitest/pretty-format': 2.1.8 '@vitest/runner': 2.1.4 '@vitest/snapshot': 2.1.4 @@ -42949,11 +43136,11 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 1.2.0 - vite: 5.4.11(@types/node@22.10.4)(terser@5.37.0) - vite-node: 2.1.4(@types/node@22.10.4)(terser@5.37.0) + vite: 5.4.11(@types/node@22.10.5)(terser@5.37.0) + vite-node: 2.1.4(@types/node@22.10.5)(terser@5.37.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.10.4 + '@types/node': 22.10.5 jsdom: 25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10) transitivePeerDependencies: - less @@ -42966,10 +43153,10 @@ snapshots: - supports-color - terser - vitest@2.1.5(@types/node@22.10.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0): + vitest@2.1.5(@types/node@22.10.5)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0): dependencies: '@vitest/expect': 2.1.5 - '@vitest/mocker': 2.1.5(vite@5.4.11(@types/node@22.10.4)(terser@5.37.0)) + '@vitest/mocker': 2.1.5(vite@5.4.11(@types/node@22.10.5)(terser@5.37.0)) '@vitest/pretty-format': 2.1.8 '@vitest/runner': 2.1.5 '@vitest/snapshot': 2.1.5 @@ -42985,11 +43172,11 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 1.2.0 - vite: 5.4.11(@types/node@22.10.4)(terser@5.37.0) - vite-node: 2.1.5(@types/node@22.10.4)(terser@5.37.0) + vite: 5.4.11(@types/node@22.10.5)(terser@5.37.0) + vite-node: 2.1.5(@types/node@22.10.5)(terser@5.37.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.10.4 + '@types/node': 22.10.5 jsdom: 25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10) transitivePeerDependencies: - less @@ -43005,7 +43192,7 @@ snapshots: vitest@2.1.5(@types/node@22.8.4)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@5.0.10))(terser@5.37.0): dependencies: '@vitest/expect': 2.1.5 - '@vitest/mocker': 2.1.5(vite@5.4.11(@types/node@22.10.4)(terser@5.37.0)) + '@vitest/mocker': 2.1.5(vite@5.4.11(@types/node@22.10.5)(terser@5.37.0)) '@vitest/pretty-format': 2.1.8 '@vitest/runner': 2.1.5 '@vitest/snapshot': 2.1.5 @@ -43074,6 +43261,16 @@ snapshots: optionalDependencies: typescript: 5.6.3 + vue@3.5.13(typescript@5.7.2): + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-sfc': 3.5.13 + '@vue/runtime-dom': 3.5.13 + '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.7.2)) + '@vue/shared': 3.5.13 + optionalDependencies: + typescript: 5.7.2 + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 @@ -43144,9 +43341,9 @@ snapshots: dependencies: web3-types: 1.10.0 - web3-eth-abi@4.4.1(typescript@5.6.3)(zod@3.23.8): + web3-eth-abi@4.4.1(typescript@5.7.2)(zod@3.23.8): dependencies: - abitype: 0.7.1(typescript@5.6.3)(zod@3.23.8) + abitype: 0.7.1(typescript@5.7.2)(zod@3.23.8) web3-errors: 1.3.1 web3-types: 1.10.0 web3-utils: 4.3.3 @@ -43165,13 +43362,13 @@ snapshots: web3-utils: 4.3.3 web3-validator: 2.0.6 - web3-eth-contract@4.7.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8): + web3-eth-contract@4.7.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8): dependencies: '@ethereumjs/rlp': 5.0.2 web3-core: 4.7.1(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) web3-errors: 1.3.1 - web3-eth: 4.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) - web3-eth-abi: 4.4.1(typescript@5.6.3)(zod@3.23.8) + web3-eth: 4.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) + web3-eth-abi: 4.4.1(typescript@5.7.2)(zod@3.23.8) web3-types: 1.10.0 web3-utils: 4.3.3 web3-validator: 2.0.6 @@ -43182,13 +43379,13 @@ snapshots: - utf-8-validate - zod - web3-eth-ens@4.4.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8): + web3-eth-ens@4.4.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8): dependencies: '@adraffy/ens-normalize': 1.11.0 web3-core: 4.7.1(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) web3-errors: 1.3.1 - web3-eth: 4.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) - web3-eth-contract: 4.7.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + web3-eth: 4.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) + web3-eth-contract: 4.7.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) web3-net: 4.1.0(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) web3-types: 1.10.0 web3-utils: 4.3.3 @@ -43207,10 +43404,10 @@ snapshots: web3-utils: 4.3.3 web3-validator: 2.0.6 - web3-eth-personal@4.1.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8): + web3-eth-personal@4.1.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8): dependencies: web3-core: 4.7.1(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) - web3-eth: 4.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + web3-eth: 4.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) web3-rpc-methods: 1.3.0(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) web3-types: 1.10.0 web3-utils: 4.3.3 @@ -43222,12 +43419,12 @@ snapshots: - utf-8-validate - zod - web3-eth@4.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8): + web3-eth@4.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8): dependencies: setimmediate: 1.0.5 web3-core: 4.7.1(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) web3-errors: 1.3.1 - web3-eth-abi: 4.4.1(typescript@5.6.3)(zod@3.23.8) + web3-eth-abi: 4.4.1(typescript@5.7.2)(zod@3.23.8) web3-eth-accounts: 4.3.1 web3-net: 4.1.0(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) web3-providers-ws: 4.0.8(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -43253,11 +43450,11 @@ snapshots: - encoding - utf-8-validate - web3-plugin-zksync@1.0.8(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.4)(typescript@5.6.3))(typescript@5.6.3)(utf-8-validate@5.0.10)(web3@4.16.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)): + web3-plugin-zksync@1.0.8(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2))(typescript@5.7.2)(utf-8-validate@5.0.10)(web3@4.16.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8)): dependencies: ethereum-cryptography: 2.2.1 - hardhat: 2.22.17(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.4)(typescript@5.6.3))(typescript@5.6.3)(utf-8-validate@5.0.10) - web3: 4.16.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + hardhat: 2.22.17(bufferutil@4.0.9)(ts-node@10.9.2(@swc/core@1.10.4(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.2))(typescript@5.7.2)(utf-8-validate@5.0.10) + web3: 4.16.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) transitivePeerDependencies: - bufferutil - c-kzg @@ -43335,17 +43532,17 @@ snapshots: web3-types: 1.10.0 zod: 3.23.8 - web3@4.16.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8): + web3@4.16.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8): dependencies: web3-core: 4.7.1(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) web3-errors: 1.3.1 - web3-eth: 4.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) - web3-eth-abi: 4.4.1(typescript@5.6.3)(zod@3.23.8) + web3-eth: 4.11.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) + web3-eth-abi: 4.4.1(typescript@5.7.2)(zod@3.23.8) web3-eth-accounts: 4.3.1 - web3-eth-contract: 4.7.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) - web3-eth-ens: 4.4.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + web3-eth-contract: 4.7.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) + web3-eth-ens: 4.4.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) web3-eth-iban: 4.0.7 - web3-eth-personal: 4.1.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + web3-eth-personal: 4.1.0(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.7.2)(utf-8-validate@5.0.10)(zod@3.23.8) web3-net: 4.1.0(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) web3-providers-http: 4.2.0(encoding@0.1.13) web3-providers-ws: 4.0.8(bufferutil@4.0.9)(utf-8-validate@5.0.10) From 3cd51a367a550751780b08a64ede4dd0c25b2e66 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Fri, 3 Jan 2025 16:22:31 -0800 Subject: [PATCH 21/33] chore: wip --- .../src/actions/get-token-info.ts | 317 ------------------ .../src/actions/search-token-info.ts | 311 +++++++++++++++++ packages/plugin-birdeye/src/index.ts | 2 +- .../plugin-birdeye/src/types/api/search.ts | 1 + packages/plugin-birdeye/src/utils.ts | 24 +- pnpm-lock.yaml | 2 +- 6 files changed, 321 insertions(+), 336 deletions(-) delete mode 100644 packages/plugin-birdeye/src/actions/get-token-info.ts create mode 100644 packages/plugin-birdeye/src/actions/search-token-info.ts diff --git a/packages/plugin-birdeye/src/actions/get-token-info.ts b/packages/plugin-birdeye/src/actions/get-token-info.ts deleted file mode 100644 index 1c274a0bec..0000000000 --- a/packages/plugin-birdeye/src/actions/get-token-info.ts +++ /dev/null @@ -1,317 +0,0 @@ -import { - Action, - ActionExample, - elizaLogger, - IAgentRuntime, - Memory, - State, -} from "@elizaos/core"; -import { BirdeyeProvider } from "../birdeye"; -import { TokenResult } from "../types/api/search"; -import { - TokenMarketDataResponse, - TokenOverviewResponse, - TokenSecurityResponse, - TokenTradeDataSingleResponse, -} from "../types/api/token"; -import { - extractChain, - extractSymbols, - formatPercentChange, - formatPrice, - formatTimestamp, - formatValue, - getTokenResultFromSearchResponse, - shortenAddress, -} from "../utils"; - -const formatTokenReport = ( - token: TokenResult | undefined, - metadata: TokenMarketDataResponse | undefined, - security: TokenSecurityResponse | undefined, - volume: TokenTradeDataSingleResponse | undefined, - overview: TokenOverviewResponse | undefined -) => { - let output = `*šŸ›”ļø Token Security and Trade Report*\n`; - output += `šŸ”– Token symbol: ${token?.symbol}\n`; - output += `šŸ”— Token Address: ${shortenAddress(token?.address)}\n\n`; - - if (security?.data) { - output += `\n`; - output += `*šŸ‘„ Ownership Distribution:*\n`; - output += `šŸ  Owner Address: ${shortenAddress(security.data.ownerAddress)}\n`; - output += `šŸ‘Øā€šŸ’¼ Creator Address: ${shortenAddress(security.data.creatorAddress)}\n`; - output += `šŸ“¦ Total Supply: ${formatValue(security.data.totalSupply)}\n`; - output += ` Mintable: ${security.data.mintable ?? "N/A"}\n`; - output += `šŸ”„ Proxied: ${security.data.proxied ?? "N/A"}\n`; - output += `šŸ”„ Proxy: ${security.data.proxy ?? "N/A"}\n`; - if (security.data.securityChecks) { - output += `šŸ” Security Checks: ${JSON.stringify(security.data.securityChecks)}\n`; - } - } - - if (volume?.data) { - output += `\n`; - output += `*šŸ“ˆ Trade Data:*\n`; - output += `šŸ‘„ Holders: ${volume.data.holder}\n`; - output += `šŸ“Š Unique Wallets (24h): ${volume.data.unique_wallet_24h}\n`; - output += `šŸ“‰ Price Change (24h): ${formatPercentChange(volume.data.price_change_24h_percent)}\n`; - output += `šŸ’ø Volume (24h USD): ${formatValue(volume.data.volume_24h_usd)}\n`; - output += `šŸ’µ Current Price: ${formatPrice(volume.data.price)}\n`; - } - - if (metadata?.data) { - output += `\n`; - output += `*šŸ“Š Market Data:*\n`; - output += `šŸ’§ Liquidity: ${formatValue(metadata.data.liquidity)}\n`; - output += `šŸ’µ Price: ${formatPrice(metadata.data.price)}\n`; - output += `šŸ“¦ Supply: ${formatValue(metadata.data.supply)}\n`; - output += `šŸ’° Market Cap: ${formatValue(metadata.data.marketcap)}\n`; - output += `šŸ”„ Circulating Supply: ${formatValue(metadata.data.circulating_supply)}\n`; - output += `šŸ’° Circulating Market Cap: ${formatValue(metadata.data.circulating_marketcap)}\n`; - } - - if (overview?.data) { - output += `\n`; - output += `*šŸ” Overview:*\n`; - output += `šŸ“ Name: ${overview.data.name}\n`; - output += `šŸ”– Symbol: ${overview.data.symbol}\n`; - output += `šŸ”¢ Decimals: ${overview.data.decimals}\n`; - if (overview.data.extensions) { - output += `šŸ”— Extensions: ${JSON.stringify(overview.data.extensions)}\n`; - } - output += `šŸ’§ Liquidity: ${formatValue(overview.data.liquidity)}\n`; - output += `ā° Last Trade Time: ${formatTimestamp(new Date(overview.data.lastTradeHumanTime).getTime() / 1000)}\n`; - output += `šŸ’µ Price: ${formatPrice(overview.data.price)}\n`; - output += `šŸ“œ Description: ${overview.data.extensions?.description ?? "N/A"}\n`; - } - - return output; -}; - -export const getTokenInfoAction = { - name: "GET_TOKEN_INFO", - similes: [ - "FIND_TOKENS", - "TOKEN_SEARCH", - "LOOKUP_TOKENS", - "CHECK_TOKEN", - "REVIEW_TOKEN", - "TOKEN_DETAILS", - "GET_TOKEN_INFO", - "TOKEN_INFO", - "TOKEN_REPORT", - "TOKEN_ANALYSIS", - "TOKEN_OVERVIEW", - "TOKEN_SUMMARY", - "TOKEN_INSIGHT", - "TOKEN_DATA", - "TOKEN_STATS", - "TOKEN_METRICS", - "TOKEN_PROFILE", - "TOKEN_REVIEW", - "TOKEN_CHECK", - "TOKEN_LOOKUP", - "TOKEN_FIND", - "TOKEN_DISCOVER", - "TOKEN_EXPLORE", - ], - description: - "Search for detailed token information including security and trade data by symbol", - handler: async ( - runtime: IAgentRuntime, - message: Memory, - state: State, - _options: any, - callback?: any - ) => { - try { - const provider = new BirdeyeProvider(runtime.cacheManager); - - const symbols = extractSymbols(message.content.text, "strict"); - - if (symbols.length === 0) { - callback?.({ text: "No token symbols found in the message" }); - return true; - } - - elizaLogger.info( - `Searching Birdeye provider for ${symbols.length} symbols` - ); - - const searchTokenResponses = symbols.map((symbol) => - provider.fetchSearchTokenMarketData({ - keyword: symbol, - sort_by: "volume_24h_usd", - sort_type: "desc", - chain: "all", - limit: 15, - }) - ); - - const results = await Promise.all(searchTokenResponses); - - // get only the token results where the symbol matches - const validResults = results.map((r, index) => - getTokenResultFromSearchResponse(r, symbols[index]) - ); - - // filter out undefined results - const filteredResults = validResults.filter( - (result): result is TokenResult => result !== undefined - ); - - if (filteredResults.length === 0) { - callback?.({ text: "No matching tokens found" }); - return true; - } - - const resultsWithChains = filteredResults.map((result) => ({ - symbol: result.symbol, - address: result.address, - chain: extractChain(result.address), - })); - - // Fetch all data in parallel for each token - const tokenData = await Promise.all( - resultsWithChains.map(async ({ address, chain }) => { - const [metadata, security, volume, overview] = - await Promise.all([ - provider.fetchTokenMarketData( - { - address, - }, - { - headers: { - "x-chain": chain, - }, - } - ), - provider.fetchTokenSecurityByAddress( - { - address, - }, - { - headers: { - "x-chain": chain, - }, - } - ), - provider.fetchTokenTradeDataSingle( - { - address, - }, - { - headers: { - "x-chain": chain, - }, - } - ), - provider.fetchTokenOverview( - { - address, - }, - { - headers: { - "x-chain": chain, - }, - } - ), - ]); - return { metadata, security, volume, overview }; - }) - ); - - const completeResults = `Found the following token information:\n\n${validResults - .map( - (result, index) => - `${formatTokenReport( - result!, - tokenData[index]?.metadata, - tokenData[index]?.security, - tokenData[index]?.volume, - tokenData[index]?.overview - )}` - ) - .join("\n\n")}`; - - callback?.({ text: completeResults }); - return true; - } catch (error) { - console.error("Error in searchTokens handler:", error.message); - callback?.({ text: `Error: ${error.message}` }); - return false; - } - }, - validate: async (_runtime: IAgentRuntime, message: Memory) => { - const symbols = extractSymbols(message.content.text, "loose"); - return symbols.length > 0; - }, - examples: [ - [ - { - user: "user", - content: { - text: "Search for $SOL and $ETH", - action: "SEARCH_TOKENS", - }, - }, - { - user: "user", - content: { - text: "Find information about $BTC", - action: "TOKEN_SEARCH", - }, - }, - { - user: "user", - content: { - text: "Look up $WETH token", - action: "LOOKUP_TOKENS", - }, - }, - { - user: "user", - content: { - text: "Tell me about SOL", - action: "CHECK_TOKEN", - }, - }, - { - user: "user", - content: { - text: "Give me details on $ADA", - action: "TOKEN_DETAILS", - }, - }, - { - user: "user", - content: { - text: "What can you tell me about $DOGE?", - action: "TOKEN_INFO", - }, - }, - { - user: "user", - content: { - text: "I need a report on $XRP", - action: "TOKEN_REPORT", - }, - }, - { - user: "user", - content: { - text: "Analyze $BNB for me", - action: "TOKEN_ANALYSIS", - }, - }, - { - user: "user", - content: { - text: "Overview of $LTC", - action: "TOKEN_OVERVIEW", - }, - }, - ], - ] as ActionExample[][], -} as Action; diff --git a/packages/plugin-birdeye/src/actions/search-token-info.ts b/packages/plugin-birdeye/src/actions/search-token-info.ts new file mode 100644 index 0000000000..d1ca6d7e97 --- /dev/null +++ b/packages/plugin-birdeye/src/actions/search-token-info.ts @@ -0,0 +1,311 @@ +import { + Action, + ActionExample, + elizaLogger, + IAgentRuntime, + Memory, + State, +} from "@elizaos/core"; +import { BirdeyeProvider } from "../birdeye"; +import { TokenResult } from "../types/api/search"; +import { + extractSymbols, + formatPercentChange, + formatPrice, + formatValue, +} from "../utils"; + +// const formatTokenReport = ( +// token: TokenResult | undefined, +// metadata: TokenMarketDataResponse | undefined, +// security: TokenSecurityResponse | undefined, +// volume: TokenTradeDataSingleResponse | undefined, +// overview: TokenOverviewResponse | undefined +// ) => { +// let output = `*šŸ›”ļø Token Security and Trade Report*\n`; +// output += `šŸ”– Token symbol: ${token?.symbol}\n`; +// output += `šŸ”— Token Address: ${shortenAddress(token?.address)}\n\n`; + +// if (security?.data) { +// output += `\n`; +// output += `*šŸ‘„ Ownership Distribution:*\n`; +// output += `šŸ  Owner Address: ${shortenAddress(security.data.ownerAddress)}\n`; +// output += `šŸ‘Øā€šŸ’¼ Creator Address: ${shortenAddress(security.data.creatorAddress)}\n`; +// output += `šŸ“¦ Total Supply: ${formatValue(security.data.totalSupply)}\n`; +// output += ` Mintable: ${security.data.mintable ?? "N/A"}\n`; +// output += `šŸ”„ Proxied: ${security.data.proxied ?? "N/A"}\n`; +// output += `šŸ”„ Proxy: ${security.data.proxy ?? "N/A"}\n`; +// if (security.data.securityChecks) { +// output += `šŸ” Security Checks: ${JSON.stringify(security.data.securityChecks)}\n`; +// } +// } + +// if (volume?.data) { +// output += `\n`; +// output += `*šŸ“ˆ Trade Data:*\n`; +// output += `šŸ‘„ Holders: ${volume.data.holder}\n`; +// output += `šŸ“Š Unique Wallets (24h): ${volume.data.unique_wallet_24h}\n`; +// output += `šŸ“‰ Price Change (24h): ${formatPercentChange(volume.data.price_change_24h_percent)}\n`; +// output += `šŸ’ø Volume (24h USD): ${formatValue(volume.data.volume_24h_usd)}\n`; +// output += `šŸ’µ Current Price: ${formatPrice(volume.data.price)}\n`; +// } + +// if (metadata?.data) { +// output += `\n`; +// output += `*šŸ“Š Market Data:*\n`; +// output += `šŸ’§ Liquidity: ${formatValue(metadata.data.liquidity)}\n`; +// output += `šŸ’µ Price: ${formatPrice(metadata.data.price)}\n`; +// output += `šŸ“¦ Supply: ${formatValue(metadata.data.supply)}\n`; +// output += `šŸ’° Market Cap: ${formatValue(metadata.data.marketcap)}\n`; +// output += `šŸ”„ Circulating Supply: ${formatValue(metadata.data.circulating_supply)}\n`; +// output += `šŸ’° Circulating Market Cap: ${formatValue(metadata.data.circulating_marketcap)}\n`; +// } + +// if (overview?.data) { +// output += `\n`; +// output += `*šŸ” Overview:*\n`; +// output += `šŸ“ Name: ${overview.data.name}\n`; +// output += `šŸ”– Symbol: ${overview.data.symbol}\n`; +// output += `šŸ”¢ Decimals: ${overview.data.decimals}\n`; +// if (overview.data.extensions) { +// output += `šŸ”— Extensions: ${JSON.stringify(overview.data.extensions)}\n`; +// } +// output += `šŸ’§ Liquidity: ${formatValue(overview.data.liquidity)}\n`; +// output += `ā° Last Trade Time: ${formatTimestamp(new Date(overview.data.lastTradeHumanTime).getTime() / 1000)}\n`; +// output += `šŸ’µ Price: ${formatPrice(overview.data.price)}\n`; +// output += `šŸ“œ Description: ${overview.data.extensions?.description ?? "N/A"}\n`; +// } + +// return output; +// }; + +const formatTokenSummary = (token: TokenResult, index: number) => { + let output = `*šŸ›”ļø Potential Match ${index + 1}*\n`; + output += `šŸ”– Symbol: ${token.symbol}\n`; + output += `šŸ”— Address: ${token.address}\n\n`; + output += `šŸŒ Network: ${token.network}\n`; + output += `šŸ’µ Price: ${formatPrice(token.price)}\n`; + output += `šŸ’ø Price Change (24h): ${formatPercentChange(token.price_change_24h_percent)}\n`; + output += `šŸ’ø Volume (24h USD): ${formatValue(token.volume_24h_usd)}\n`; + output += `šŸ’° Market Cap: ${formatValue(token.market_cap)}\n`; + return output; +}; + +export const getTokenInfoAction = { + name: "GET_TOKEN_INFO", + similes: [ + "FIND_TOKENS", + "TOKEN_SEARCH", + "LOOKUP_TOKENS", + "CHECK_TOKEN", + "REVIEW_TOKEN", + "TOKEN_DETAILS", + "GET_TOKEN_INFO", + "TOKEN_INFO", + "TOKEN_REPORT", + "TOKEN_ANALYSIS", + "TOKEN_OVERVIEW", + "TOKEN_SUMMARY", + "TOKEN_INSIGHT", + "TOKEN_DATA", + "TOKEN_STATS", + "TOKEN_METRICS", + "TOKEN_PROFILE", + "TOKEN_REVIEW", + "TOKEN_CHECK", + "TOKEN_LOOKUP", + "TOKEN_FIND", + "TOKEN_DISCOVER", + "TOKEN_EXPLORE", + ], + description: + "Search for detailed token information including security and trade data by symbol", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: any, + callback?: any + ) => { + try { + const provider = new BirdeyeProvider(runtime.cacheManager); + + // get all symbols from the message that match (i.e. $SOL, $ETH, $BTC, etc.). If you want to match more loosely, use "loose" instead of "strict" and it will match $SOL, SOL, SOLANA, etc. + const symbols = extractSymbols(message.content.text, "strict"); + + if (symbols.length === 0) { + callback?.({ text: "No token symbols found in the message" }); + return true; + } + + elizaLogger.info( + `Searching Birdeye provider for ${symbols.length} symbols` + ); + + // for each symbol, do a search in Birdeye. This will return a list of token results that may be amatch to the token symbol. + const results = await Promise.all( + symbols.map((symbol) => + provider.fetchSearchTokenMarketData({ + keyword: symbol, + sort_by: "volume_24h_usd", + sort_type: "desc", + chain: "all", + // by default we respond with the top 5 results + limit: 5, + }) + ) + ); + + // get only the token results + const validResults = results.map((r) => + r.data.items + .filter((item) => item.type === "token") + .flatMap((item) => item.result) + ) as TokenResult[]; + + if (validResults.length === 0) { + callback?.({ text: "No matching tokens found" }); + return true; + } + + // // get the chain for each token result + // const resultsWithChains = validResults.map((result) => ({ + // symbol: result.symbol, + // address: result.address, + // chain: result.network, + // })); + + // Fetch all data in parallel for each token + // const tokenData = await Promise.all( + // resultsWithChains.map(async ({ address, chain }) => { + // const [metadata, security, volume, overview] = + // await Promise.all([ + // provider.fetchTokenMarketData( + // { + // address, + // }, + // { + // headers: { + // "x-chain": chain, + // }, + // } + // ), + // provider.fetchTokenSecurityByAddress( + // { + // address, + // }, + // { + // headers: { + // "x-chain": chain, + // }, + // } + // ), + // provider.fetchTokenTradeDataSingle( + // { + // address, + // }, + // { + // headers: { + // "x-chain": chain, + // }, + // } + // ), + // provider.fetchTokenOverview( + // { + // address, + // }, + // { + // headers: { + // "x-chain": chain, + // }, + // } + // ), + // ]); + // return { metadata, security, volume, overview }; + // }) + // ); + + const completeResults = `Found the following tokens that could be a match information:\n\n${validResults + .map((result, index) => `${formatTokenSummary(result, index)}`) + .join("\n\n")}`; + + callback?.({ text: completeResults }); + return true; + } catch (error) { + console.error("Error in searchTokens handler:", error.message); + callback?.({ text: `Error: ${error.message}` }); + return false; + } + }, + validate: async (_runtime: IAgentRuntime, message: Memory) => { + const symbols = extractSymbols(message.content.text, "loose"); + return symbols.length > 0; + }, + examples: [ + [ + { + user: "user", + content: { + text: "Search for $SOL and $ETH", + action: "SEARCH_TOKENS", + }, + }, + { + user: "user", + content: { + text: "Find information about $BTC", + action: "TOKEN_SEARCH", + }, + }, + { + user: "user", + content: { + text: "Look up $WETH token", + action: "LOOKUP_TOKENS", + }, + }, + { + user: "user", + content: { + text: "Tell me about SOL", + action: "CHECK_TOKEN", + }, + }, + { + user: "user", + content: { + text: "Give me details on $ADA", + action: "TOKEN_DETAILS", + }, + }, + { + user: "user", + content: { + text: "What can you tell me about $DOGE?", + action: "TOKEN_INFO", + }, + }, + { + user: "user", + content: { + text: "I need a report on $XRP", + action: "TOKEN_REPORT", + }, + }, + { + user: "user", + content: { + text: "Analyze $BNB for me", + action: "TOKEN_ANALYSIS", + }, + }, + { + user: "user", + content: { + text: "Overview of $LTC", + action: "TOKEN_OVERVIEW", + }, + }, + ], + ] as ActionExample[][], +} as Action; diff --git a/packages/plugin-birdeye/src/index.ts b/packages/plugin-birdeye/src/index.ts index d066a67d54..14fd2b2b71 100644 --- a/packages/plugin-birdeye/src/index.ts +++ b/packages/plugin-birdeye/src/index.ts @@ -1,6 +1,6 @@ import { Plugin } from "@elizaos/core"; -import { getTokenInfoAction } from "./actions/get-token-info"; import { getWalletInfoAction } from "./actions/get-wallet-info"; +import { getTokenInfoAction } from "./actions/search-token-info"; import { agentPortfolioProvider } from "./providers/agent-portfolio-provider"; export const birdeyePlugin: Plugin = { diff --git a/packages/plugin-birdeye/src/types/api/search.ts b/packages/plugin-birdeye/src/types/api/search.ts index dd1ab8d520..bdfd5410ef 100644 --- a/packages/plugin-birdeye/src/types/api/search.ts +++ b/packages/plugin-birdeye/src/types/api/search.ts @@ -43,6 +43,7 @@ export interface TokenResult { name?: string; symbol?: string; address?: string; + network?: string; fdv?: number; market_cap?: number; liquidity?: number; diff --git a/packages/plugin-birdeye/src/utils.ts b/packages/plugin-birdeye/src/utils.ts index a553792ef4..979d490508 100644 --- a/packages/plugin-birdeye/src/utils.ts +++ b/packages/plugin-birdeye/src/utils.ts @@ -457,6 +457,8 @@ export const extractSymbols = ( ? [ // $SYMBOL format /\$([A-Z0-9]{2,10})\b/gi, + // $SYMBOL format with lowercase + /\$([a-z0-9]{2,10})\b/gi, ] : [ // $SYMBOL format @@ -603,21 +605,9 @@ export const convertToStringParams = (params: BirdeyeApiParams) => { }; export const getTokenResultFromSearchResponse = ( - response: TokenMarketSearchResponse, - symbol: string -): TokenResult | undefined => { - const tokenResponses: TokenMarketSearchResponse["data"]["items"] = - response.data.items.filter((item) => item.type === "token"); - - return tokenResponses - .map((item) => item.result) - .flat() - .find( - (r: TokenResult): r is TokenResult => - r.symbol.toLowerCase() === symbol.toLowerCase() && - // only show tokens with liquidity, fdv, and price to help filter out junk - Boolean(r.liquidity) && - Boolean(r.fdv) && - Boolean(r.price) - ); + response: TokenMarketSearchResponse +): TokenResult[] | undefined => { + return response.data.items + .filter((item) => item.type === "token") + .flatMap((item) => item.result); }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 36c9857b56..b46dca3188 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33548,7 +33548,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.3.4 get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: From 63c7c9c7386ddb860af3b0cc820f68b71f568332 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Fri, 3 Jan 2025 16:58:27 -0800 Subject: [PATCH 22/33] chore: cleanup formatting --- client/src/Chat.tsx | 6 ++--- .../src/actions/search-token-info.ts | 27 ++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/client/src/Chat.tsx b/client/src/Chat.tsx index f9538d1323..e1b8d1c71e 100644 --- a/client/src/Chat.tsx +++ b/client/src/Chat.tsx @@ -92,8 +92,8 @@ export default function Chat() { : "justify-start" }`} > -
) ))} -
+ )) ) : ( diff --git a/packages/plugin-birdeye/src/actions/search-token-info.ts b/packages/plugin-birdeye/src/actions/search-token-info.ts index d1ca6d7e97..6eef31bb7b 100644 --- a/packages/plugin-birdeye/src/actions/search-token-info.ts +++ b/packages/plugin-birdeye/src/actions/search-token-info.ts @@ -79,16 +79,19 @@ import { // return output; // }; -const formatTokenSummary = (token: TokenResult, index: number) => { - let output = `*šŸ›”ļø Potential Match ${index + 1}*\n`; - output += `šŸ”– Symbol: ${token.symbol}\n`; - output += `šŸ”— Address: ${token.address}\n\n`; - output += `šŸŒ Network: ${token.network}\n`; - output += `šŸ’µ Price: ${formatPrice(token.price)}\n`; - output += `šŸ’ø Price Change (24h): ${formatPercentChange(token.price_change_24h_percent)}\n`; - output += `šŸ’ø Volume (24h USD): ${formatValue(token.volume_24h_usd)}\n`; - output += `šŸ’° Market Cap: ${formatValue(token.market_cap)}\n`; - return output; +const formatTokenSummary = (tokens: TokenResult[]) => { + return tokens + .map((token, index) => { + let output = `*šŸ›”ļø Potential Match ${index + 1}*\n`; + output += `šŸ”– Symbol: ${token.symbol}\n`; + output += `šŸ”— Address: ${token.address}\n\n`; + output += `šŸŒ Network: ${token.network}\n`; + output += `šŸ’µ Price: ${formatPrice(token.price)} (${formatPercentChange(token.price_change_24h_percent)})\n`; + output += `šŸ’ø Volume (24h USD): ${formatValue(token.volume_24h_usd)}\n`; + output += `šŸ’° Market Cap: ${formatValue(token.market_cap)}\n`; + return output; + }) + .join("\n\n"); }; export const getTokenInfoAction = { @@ -161,7 +164,7 @@ export const getTokenInfoAction = { r.data.items .filter((item) => item.type === "token") .flatMap((item) => item.result) - ) as TokenResult[]; + ) as TokenResult[][]; if (validResults.length === 0) { callback?.({ text: "No matching tokens found" }); @@ -226,7 +229,7 @@ export const getTokenInfoAction = { // ); const completeResults = `Found the following tokens that could be a match information:\n\n${validResults - .map((result, index) => `${formatTokenSummary(result, index)}`) + .map((result) => `${formatTokenSummary(result)}`) .join("\n\n")}`; callback?.({ text: completeResults }); From 831140b378258cd8cfe6b4366d3f32cefd56ba0b Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Sat, 4 Jan 2025 06:58:10 -0800 Subject: [PATCH 23/33] chore: got token address and symbol search working --- .../src/actions/get-wallet-info.ts | 4 +- .../src/actions/search-token-info.ts | 314 ----------------- .../src/actions/test-all-endpoints.ts | 1 + .../src/actions/token-search-address.ts | 315 ++++++++++++++++++ .../src/actions/token-search-symbol.ts | 221 ++++++++++++ packages/plugin-birdeye/src/index.ts | 6 +- packages/plugin-birdeye/src/utils.ts | 4 +- 7 files changed, 545 insertions(+), 320 deletions(-) delete mode 100644 packages/plugin-birdeye/src/actions/search-token-info.ts create mode 100644 packages/plugin-birdeye/src/actions/token-search-address.ts create mode 100644 packages/plugin-birdeye/src/actions/token-search-symbol.ts diff --git a/packages/plugin-birdeye/src/actions/get-wallet-info.ts b/packages/plugin-birdeye/src/actions/get-wallet-info.ts index d1f72f297f..d11b58c4e8 100644 --- a/packages/plugin-birdeye/src/actions/get-wallet-info.ts +++ b/packages/plugin-birdeye/src/actions/get-wallet-info.ts @@ -10,7 +10,7 @@ import { type State, } from "@elizaos/core"; import { BirdeyeProvider } from "../birdeye"; -import { extractAddressesFromString } from "../utils"; +import { extractAddresses } from "../utils"; const extractWalletAddressTemplate = `Given the recent message below: {{recentMessages}} @@ -144,7 +144,7 @@ export const getWalletInfoAction = { }, validate: async (_runtime: IAgentRuntime, message: Memory) => { // Check if the message contains any potential wallet addresses - const addresses = extractAddressesFromString(message.content.text); + const addresses = extractAddresses(message.content.text); return addresses.length > 0; }, examples: [ diff --git a/packages/plugin-birdeye/src/actions/search-token-info.ts b/packages/plugin-birdeye/src/actions/search-token-info.ts deleted file mode 100644 index 6eef31bb7b..0000000000 --- a/packages/plugin-birdeye/src/actions/search-token-info.ts +++ /dev/null @@ -1,314 +0,0 @@ -import { - Action, - ActionExample, - elizaLogger, - IAgentRuntime, - Memory, - State, -} from "@elizaos/core"; -import { BirdeyeProvider } from "../birdeye"; -import { TokenResult } from "../types/api/search"; -import { - extractSymbols, - formatPercentChange, - formatPrice, - formatValue, -} from "../utils"; - -// const formatTokenReport = ( -// token: TokenResult | undefined, -// metadata: TokenMarketDataResponse | undefined, -// security: TokenSecurityResponse | undefined, -// volume: TokenTradeDataSingleResponse | undefined, -// overview: TokenOverviewResponse | undefined -// ) => { -// let output = `*šŸ›”ļø Token Security and Trade Report*\n`; -// output += `šŸ”– Token symbol: ${token?.symbol}\n`; -// output += `šŸ”— Token Address: ${shortenAddress(token?.address)}\n\n`; - -// if (security?.data) { -// output += `\n`; -// output += `*šŸ‘„ Ownership Distribution:*\n`; -// output += `šŸ  Owner Address: ${shortenAddress(security.data.ownerAddress)}\n`; -// output += `šŸ‘Øā€šŸ’¼ Creator Address: ${shortenAddress(security.data.creatorAddress)}\n`; -// output += `šŸ“¦ Total Supply: ${formatValue(security.data.totalSupply)}\n`; -// output += ` Mintable: ${security.data.mintable ?? "N/A"}\n`; -// output += `šŸ”„ Proxied: ${security.data.proxied ?? "N/A"}\n`; -// output += `šŸ”„ Proxy: ${security.data.proxy ?? "N/A"}\n`; -// if (security.data.securityChecks) { -// output += `šŸ” Security Checks: ${JSON.stringify(security.data.securityChecks)}\n`; -// } -// } - -// if (volume?.data) { -// output += `\n`; -// output += `*šŸ“ˆ Trade Data:*\n`; -// output += `šŸ‘„ Holders: ${volume.data.holder}\n`; -// output += `šŸ“Š Unique Wallets (24h): ${volume.data.unique_wallet_24h}\n`; -// output += `šŸ“‰ Price Change (24h): ${formatPercentChange(volume.data.price_change_24h_percent)}\n`; -// output += `šŸ’ø Volume (24h USD): ${formatValue(volume.data.volume_24h_usd)}\n`; -// output += `šŸ’µ Current Price: ${formatPrice(volume.data.price)}\n`; -// } - -// if (metadata?.data) { -// output += `\n`; -// output += `*šŸ“Š Market Data:*\n`; -// output += `šŸ’§ Liquidity: ${formatValue(metadata.data.liquidity)}\n`; -// output += `šŸ’µ Price: ${formatPrice(metadata.data.price)}\n`; -// output += `šŸ“¦ Supply: ${formatValue(metadata.data.supply)}\n`; -// output += `šŸ’° Market Cap: ${formatValue(metadata.data.marketcap)}\n`; -// output += `šŸ”„ Circulating Supply: ${formatValue(metadata.data.circulating_supply)}\n`; -// output += `šŸ’° Circulating Market Cap: ${formatValue(metadata.data.circulating_marketcap)}\n`; -// } - -// if (overview?.data) { -// output += `\n`; -// output += `*šŸ” Overview:*\n`; -// output += `šŸ“ Name: ${overview.data.name}\n`; -// output += `šŸ”– Symbol: ${overview.data.symbol}\n`; -// output += `šŸ”¢ Decimals: ${overview.data.decimals}\n`; -// if (overview.data.extensions) { -// output += `šŸ”— Extensions: ${JSON.stringify(overview.data.extensions)}\n`; -// } -// output += `šŸ’§ Liquidity: ${formatValue(overview.data.liquidity)}\n`; -// output += `ā° Last Trade Time: ${formatTimestamp(new Date(overview.data.lastTradeHumanTime).getTime() / 1000)}\n`; -// output += `šŸ’µ Price: ${formatPrice(overview.data.price)}\n`; -// output += `šŸ“œ Description: ${overview.data.extensions?.description ?? "N/A"}\n`; -// } - -// return output; -// }; - -const formatTokenSummary = (tokens: TokenResult[]) => { - return tokens - .map((token, index) => { - let output = `*šŸ›”ļø Potential Match ${index + 1}*\n`; - output += `šŸ”– Symbol: ${token.symbol}\n`; - output += `šŸ”— Address: ${token.address}\n\n`; - output += `šŸŒ Network: ${token.network}\n`; - output += `šŸ’µ Price: ${formatPrice(token.price)} (${formatPercentChange(token.price_change_24h_percent)})\n`; - output += `šŸ’ø Volume (24h USD): ${formatValue(token.volume_24h_usd)}\n`; - output += `šŸ’° Market Cap: ${formatValue(token.market_cap)}\n`; - return output; - }) - .join("\n\n"); -}; - -export const getTokenInfoAction = { - name: "GET_TOKEN_INFO", - similes: [ - "FIND_TOKENS", - "TOKEN_SEARCH", - "LOOKUP_TOKENS", - "CHECK_TOKEN", - "REVIEW_TOKEN", - "TOKEN_DETAILS", - "GET_TOKEN_INFO", - "TOKEN_INFO", - "TOKEN_REPORT", - "TOKEN_ANALYSIS", - "TOKEN_OVERVIEW", - "TOKEN_SUMMARY", - "TOKEN_INSIGHT", - "TOKEN_DATA", - "TOKEN_STATS", - "TOKEN_METRICS", - "TOKEN_PROFILE", - "TOKEN_REVIEW", - "TOKEN_CHECK", - "TOKEN_LOOKUP", - "TOKEN_FIND", - "TOKEN_DISCOVER", - "TOKEN_EXPLORE", - ], - description: - "Search for detailed token information including security and trade data by symbol", - handler: async ( - runtime: IAgentRuntime, - message: Memory, - state: State, - _options: any, - callback?: any - ) => { - try { - const provider = new BirdeyeProvider(runtime.cacheManager); - - // get all symbols from the message that match (i.e. $SOL, $ETH, $BTC, etc.). If you want to match more loosely, use "loose" instead of "strict" and it will match $SOL, SOL, SOLANA, etc. - const symbols = extractSymbols(message.content.text, "strict"); - - if (symbols.length === 0) { - callback?.({ text: "No token symbols found in the message" }); - return true; - } - - elizaLogger.info( - `Searching Birdeye provider for ${symbols.length} symbols` - ); - - // for each symbol, do a search in Birdeye. This will return a list of token results that may be amatch to the token symbol. - const results = await Promise.all( - symbols.map((symbol) => - provider.fetchSearchTokenMarketData({ - keyword: symbol, - sort_by: "volume_24h_usd", - sort_type: "desc", - chain: "all", - // by default we respond with the top 5 results - limit: 5, - }) - ) - ); - - // get only the token results - const validResults = results.map((r) => - r.data.items - .filter((item) => item.type === "token") - .flatMap((item) => item.result) - ) as TokenResult[][]; - - if (validResults.length === 0) { - callback?.({ text: "No matching tokens found" }); - return true; - } - - // // get the chain for each token result - // const resultsWithChains = validResults.map((result) => ({ - // symbol: result.symbol, - // address: result.address, - // chain: result.network, - // })); - - // Fetch all data in parallel for each token - // const tokenData = await Promise.all( - // resultsWithChains.map(async ({ address, chain }) => { - // const [metadata, security, volume, overview] = - // await Promise.all([ - // provider.fetchTokenMarketData( - // { - // address, - // }, - // { - // headers: { - // "x-chain": chain, - // }, - // } - // ), - // provider.fetchTokenSecurityByAddress( - // { - // address, - // }, - // { - // headers: { - // "x-chain": chain, - // }, - // } - // ), - // provider.fetchTokenTradeDataSingle( - // { - // address, - // }, - // { - // headers: { - // "x-chain": chain, - // }, - // } - // ), - // provider.fetchTokenOverview( - // { - // address, - // }, - // { - // headers: { - // "x-chain": chain, - // }, - // } - // ), - // ]); - // return { metadata, security, volume, overview }; - // }) - // ); - - const completeResults = `Found the following tokens that could be a match information:\n\n${validResults - .map((result) => `${formatTokenSummary(result)}`) - .join("\n\n")}`; - - callback?.({ text: completeResults }); - return true; - } catch (error) { - console.error("Error in searchTokens handler:", error.message); - callback?.({ text: `Error: ${error.message}` }); - return false; - } - }, - validate: async (_runtime: IAgentRuntime, message: Memory) => { - const symbols = extractSymbols(message.content.text, "loose"); - return symbols.length > 0; - }, - examples: [ - [ - { - user: "user", - content: { - text: "Search for $SOL and $ETH", - action: "SEARCH_TOKENS", - }, - }, - { - user: "user", - content: { - text: "Find information about $BTC", - action: "TOKEN_SEARCH", - }, - }, - { - user: "user", - content: { - text: "Look up $WETH token", - action: "LOOKUP_TOKENS", - }, - }, - { - user: "user", - content: { - text: "Tell me about SOL", - action: "CHECK_TOKEN", - }, - }, - { - user: "user", - content: { - text: "Give me details on $ADA", - action: "TOKEN_DETAILS", - }, - }, - { - user: "user", - content: { - text: "What can you tell me about $DOGE?", - action: "TOKEN_INFO", - }, - }, - { - user: "user", - content: { - text: "I need a report on $XRP", - action: "TOKEN_REPORT", - }, - }, - { - user: "user", - content: { - text: "Analyze $BNB for me", - action: "TOKEN_ANALYSIS", - }, - }, - { - user: "user", - content: { - text: "Overview of $LTC", - action: "TOKEN_OVERVIEW", - }, - }, - ], - ] as ActionExample[][], -} as Action; diff --git a/packages/plugin-birdeye/src/actions/test-all-endpoints.ts b/packages/plugin-birdeye/src/actions/test-all-endpoints.ts index 9c23b653bc..a2ed875875 100644 --- a/packages/plugin-birdeye/src/actions/test-all-endpoints.ts +++ b/packages/plugin-birdeye/src/actions/test-all-endpoints.ts @@ -9,6 +9,7 @@ import { import { BirdeyeProvider } from "../birdeye"; import { waitFor } from "../utils"; +// This is a dummy action generated solely to test all Birdeye endpoints and should not be used in production export const testAllEndpointsAction = { name: "BIRDEYE_TEST_ALL_ENDPOINTS", similes: [], diff --git a/packages/plugin-birdeye/src/actions/token-search-address.ts b/packages/plugin-birdeye/src/actions/token-search-address.ts new file mode 100644 index 0000000000..8211228667 --- /dev/null +++ b/packages/plugin-birdeye/src/actions/token-search-address.ts @@ -0,0 +1,315 @@ +import { + Action, + ActionExample, + elizaLogger, + formatTimestamp, + IAgentRuntime, + Memory, + State, +} from "@elizaos/core"; +import { BirdeyeProvider } from "../birdeye"; +import { + TokenMarketDataResponse, + TokenOverviewResponse, + TokenSecurityResponse, + TokenTradeDataSingleResponse, +} from "../types/api/token"; +import { BaseAddress } from "../types/shared"; +import { + extractAddresses, + formatPercentChange, + formatPrice, + formatValue, + shortenAddress, +} from "../utils"; + +type TokenAddressSearchResult = { + overview: TokenOverviewResponse; + tradeData: TokenTradeDataSingleResponse; + security: TokenSecurityResponse; + marketData: TokenMarketDataResponse; +}; + +export const tokenSearchAddressAction = { + name: "TOKEN_SEARCH_ADDRESS", + similes: [ + "SEARCH_TOKEN_ADDRESS", + "FIND_TOKEN_ADDRESS", + "LOOKUP_TOKEN_ADDRESS", + "CHECK_TOKEN_ADDRESS", + "GET_TOKEN_BY_ADDRESS", + "TOKEN_ADDRESS_INFO", + "TOKEN_ADDRESS_LOOKUP", + "TOKEN_ADDRESS_SEARCH", + "TOKEN_ADDRESS_CHECK", + "TOKEN_ADDRESS_DETAILS", + "TOKEN_CONTRACT_SEARCH", + "TOKEN_CONTRACT_LOOKUP", + "TOKEN_CONTRACT_INFO", + "TOKEN_CONTRACT_CHECK", + "VERIFY_TOKEN_ADDRESS", + "VALIDATE_TOKEN_ADDRESS", + "GET_TOKEN_INFO", + "TOKEN_INFO", + "TOKEN_REPORT", + "TOKEN_ANALYSIS", + "TOKEN_OVERVIEW", + "TOKEN_SUMMARY", + "TOKEN_INSIGHT", + "TOKEN_DATA", + "TOKEN_STATS", + "TOKEN_METRICS", + "TOKEN_PROFILE", + "TOKEN_REVIEW", + "TOKEN_CHECK", + "TOKEN_LOOKUP", + "TOKEN_FIND", + "TOKEN_DISCOVER", + "TOKEN_EXPLORE", + ], + description: + "Search for detailed token information including security and trade data by address", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: any, + callback?: any + ) => { + try { + const provider = new BirdeyeProvider(runtime.cacheManager); + + // get all contract addresses from the message + const addresses = extractAddresses(message.content.text); + + elizaLogger.info( + `Searching Birdeye provider for ${addresses.length} addresses` + ); + + // for each symbol, do a search in Birdeye. This will return a list of token results that may be amatch to the token symbol. + const results: TokenAddressSearchResult[] = await Promise.all( + addresses.map(async ({ address, chain: addressChain }) => { + // address detection can't distinguish between evm chains, so we currently only do address search on ETH for EVM addresses. Future support will be added for other chains if the user requests it. + const chain = + addressChain === "evm" ? "ethereum" : addressChain; + + const [overview, marketData, security, tradeData] = + await Promise.all([ + provider.fetchTokenOverview( + { + address, + }, + { + headers: { + "x-chain": chain, + }, + } + ), + provider.fetchTokenMarketData( + { + address, + }, + { + headers: { + "x-chain": chain, + }, + } + ), + provider.fetchTokenSecurityByAddress( + { + address, + }, + { + headers: { + "x-chain": chain, + }, + } + ), + provider.fetchTokenTradeDataSingle( + { + address, + }, + { + headers: { + "x-chain": chain, + }, + } + ), + ]); + + return { + overview, + marketData, + security, + tradeData, + }; + }) + ); + + console.log(results); + + const completeResults = `I performed a search for the token symbols you requested and found the following results (for more details search by contract address):\n\n${results + .map( + (result, i) => + `${formatTokenReport(addresses[i], i, result)}` + ) + .join("\n\n")}`; + + callback?.({ text: completeResults }); + return true; + } catch (error) { + console.error("Error in searchTokens handler:", error.message); + callback?.({ text: `Error: ${error.message}` }); + return false; + } + }, + validate: async (_runtime: IAgentRuntime, message: Memory) => { + const addresses = extractAddresses(message.content.text); + return addresses.length > 0; + }, + examples: [ + [ + { + user: "user", + content: { + text: "Search for $SOL and $ETH", + action: "SEARCH_TOKENS", + }, + }, + { + user: "user", + content: { + text: "Find information about $BTC", + action: "TOKEN_SEARCH", + }, + }, + { + user: "user", + content: { + text: "Look up $WETH token", + action: "LOOKUP_TOKENS", + }, + }, + { + user: "user", + content: { + text: "Tell me about SOL", + action: "CHECK_TOKEN", + }, + }, + { + user: "user", + content: { + text: "Give me details on $ADA", + action: "TOKEN_DETAILS", + }, + }, + { + user: "user", + content: { + text: "What can you tell me about $DOGE?", + action: "TOKEN_INFO", + }, + }, + { + user: "user", + content: { + text: "I need a report on $XRP", + action: "TOKEN_REPORT", + }, + }, + { + user: "user", + content: { + text: "Analyze $BNB for me", + action: "TOKEN_ANALYSIS", + }, + }, + { + user: "user", + content: { + text: "Overview of $LTC", + action: "TOKEN_OVERVIEW", + }, + }, + ], + ] as ActionExample[][], +} as Action; + +// take all the details of the results and present to the user +const formatTokenReport = ( + address: BaseAddress, + index: number, + result: TokenAddressSearchResult +) => { + let output = ``; + + if (result.overview?.data) { + output += `\n`; + output += `Token Overview:\n`; + output += `šŸ“ Name: ${result.overview.data.name}\n`; + output += `šŸ”– Symbol: ${result.overview.data.symbol}\n`; + output += `šŸ”— Address: ${address.address}\n`; + output += `šŸ”¢ Decimals: ${result.overview.data.decimals}\n`; + output += ``; + if (result.overview.data.extensions) { + const ext = result.overview.data.extensions; + output += `šŸ”— Links & Info:\n`; + if (ext.website) output += ` ā€¢ Website: ${ext.website}\n`; + if (ext.twitter) output += ` ā€¢ Twitter: ${ext.twitter}\n`; + if (ext.telegram) output += ` ā€¢ Telegram: ${ext.telegram}\n`; + if (ext.discord) output += ` ā€¢ Discord: ${ext.discord}\n`; + if (ext.medium) output += ` ā€¢ Medium: ${ext.medium}\n`; + if (ext.coingeckoId) + output += ` ā€¢ CoinGecko ID: ${ext.coingeckoId}\n`; + if (ext.serumV3Usdc) + output += ` ā€¢ Serum V3 USDC: ${ext.serumV3Usdc}\n`; + if (ext.serumV3Usdt) + output += ` ā€¢ Serum V3 USDT: ${ext.serumV3Usdt}\n`; + } + output += `šŸ’§ Liquidity: ${formatValue(result.overview.data.liquidity)}\n`; + output += `ā° Last Trade Time: ${formatTimestamp(new Date(result.overview.data.lastTradeHumanTime).getTime() / 1000)}\n`; + output += `šŸ’µ Price: ${formatPrice(result.overview.data.price)}\n`; + output += `šŸ“œ Description: ${result.overview.data.extensions?.description ?? "N/A"}\n`; + } + + if (result.marketData?.data) { + output += `\n`; + output += `Market Data:\n`; + output += `šŸ’§ Liquidity: ${formatValue(result.marketData.data.liquidity)}\n`; + output += `šŸ’µ Price: ${formatPrice(result.marketData.data.price)}\n`; + output += `šŸ“¦ Supply: ${formatValue(result.marketData.data.supply)}\n`; + output += `šŸ’° Market Cap: ${formatValue(result.marketData.data.marketcap)}\n`; + output += `šŸ”„ Circulating Supply: ${formatValue(result.marketData.data.circulating_supply)}\n`; + output += `šŸ’° Circulating Market Cap: ${formatValue(result.marketData.data.circulating_marketcap)}\n`; + } + + if (result.tradeData?.data) { + output += `\n`; + output += `Trade Data:\n`; + output += `šŸ‘„ Holders: ${result.tradeData.data.holder}\n`; + output += `šŸ“Š Unique Wallets (24h): ${result.tradeData.data.unique_wallet_24h}\n`; + output += `šŸ“‰ Price Change (24h): ${formatPercentChange(result.tradeData.data.price_change_24h_percent)}\n`; + output += `šŸ’ø Volume (24h USD): ${formatValue(result.tradeData.data.volume_24h_usd)}\n`; + output += `šŸ’µ Current Price: $${formatPrice(result.tradeData.data.price)}\n`; + } + + if (result.security?.data) { + output += `\n`; + output += `Ownership Distribution:\n`; + output += `šŸ  Owner Address: ${shortenAddress(result.security.data.ownerAddress)}\n`; + output += `šŸ‘Øā€šŸ’¼ Creator Address: ${shortenAddress(result.security.data.creatorAddress)}\n`; + output += `šŸ“¦ Total Supply: ${formatValue(result.security.data.totalSupply)}\n`; + output += result.security.data.proxied + ? `šŸŒæ Mintable: ${result.security.data.mintable ?? "N/A"}\n` + : ""; + output += result.security.data.proxy + ? `šŸ”„ Proxied: ${result.security.data.proxy ?? "N/A"}\n` + : ""; + output += result.security.data.securityChecks + ? `šŸ” Security Checks: ${JSON.stringify(result.security.data.securityChecks)}\n` + : ""; + } + + return output ?? `No results found for ${address.address}`; +}; diff --git a/packages/plugin-birdeye/src/actions/token-search-symbol.ts b/packages/plugin-birdeye/src/actions/token-search-symbol.ts new file mode 100644 index 0000000000..f6923f651c --- /dev/null +++ b/packages/plugin-birdeye/src/actions/token-search-symbol.ts @@ -0,0 +1,221 @@ +import { + Action, + ActionExample, + elizaLogger, + IAgentRuntime, + Memory, + State, +} from "@elizaos/core"; +import { BirdeyeProvider } from "../birdeye"; +import { TokenResult } from "../types/api/search"; +import { + extractSymbols, + formatPercentChange, + formatPrice, + formatValue, +} from "../utils"; + +// "strict" requires a $ prefix and will match $SOL, $ai16z, $BTC, etc. +// "loose" will match $SOL, SOL, SOLANA, etc. and does not require a $ prefix but may interpret any other acronyms as symbols to search for +const SYMBOL_SEARCH_MODE = "strict"; + +export const tokenSearchSymbolAction = { + name: "TOKEN_SEARCH_SYMBOL", + similes: [ + "SEARCH_TOKEN_SYMBOL", + "FIND_TOKEN_SYMBOL", + "LOOKUP_TOKEN_SYMBOL", + "CHECK_TOKEN_SYMBOL", + "GET_TOKEN_BY_SYMBOL", + "SYMBOL_SEARCH", + "SYMBOL_LOOKUP", + "SYMBOL_CHECK", + "TOKEN_SYMBOL_INFO", + "TOKEN_SYMBOL_DETAILS", + "TOKEN_SYMBOL_LOOKUP", + "TOKEN_SYMBOL_SEARCH", + "TOKEN_SYMBOL_CHECK", + "TOKEN_SYMBOL_QUERY", + "TOKEN_SYMBOL_FIND", + "GET_TOKEN_INFO", + "TOKEN_INFO", + "TOKEN_REPORT", + "TOKEN_ANALYSIS", + "TOKEN_OVERVIEW", + "TOKEN_SUMMARY", + "TOKEN_INSIGHT", + "TOKEN_DATA", + "TOKEN_STATS", + "TOKEN_METRICS", + "TOKEN_PROFILE", + "TOKEN_REVIEW", + "TOKEN_CHECK", + "TOKEN_LOOKUP", + "TOKEN_FIND", + "TOKEN_DISCOVER", + "TOKEN_EXPLORE", + ], + description: + "Search for detailed token information including security and trade data by symbol", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: any, + callback?: any + ) => { + try { + const provider = new BirdeyeProvider(runtime.cacheManager); + + // get all symbols from the message that match (i.e. $SOL, $ETH, $BTC, etc.). If you want to match more loosely, use "loose" instead of "strict" and it will match $SOL, SOL, SOLANA, etc. + const symbols = extractSymbols( + message.content.text, + SYMBOL_SEARCH_MODE + ); + + elizaLogger.info( + `Searching Birdeye provider for ${symbols.length} symbols` + ); + + // for each symbol, do a search in Birdeye. This will return a list of token results that may be amatch to the token symbol. + const results = await Promise.all( + symbols.map((symbol) => + provider.fetchSearchTokenMarketData({ + keyword: symbol, + sort_by: "volume_24h_usd", + sort_type: "desc", + chain: "all", + limit: 5, + }) + ) + ); + + // get filter the resuls to only include the token results and then filter the results to only include the ones that match the symbol + const validResults = results.map((r, i) => + r.data.items + .filter((item) => item.type === "token" && item.result) + .flatMap((item) => + (item.result as TokenResult[]).filter( + (r) => + r.symbol?.toLowerCase() === + symbols[i].toLowerCase() + ) + ) + ) as TokenResult[][]; + + if (validResults.length === 0) { + return true; + } + + const completeResults = `I performed a search for the token symbols you requested and found the following results (for more details search by contract address):\n\n${validResults + .map( + (result, i) => + `${formatTokenSummary(symbols[i], i, result)}` + ) + .join("\n\n")}`; + + callback?.({ text: completeResults }); + return true; + } catch (error) { + console.error("Error in searchTokens handler:", error.message); + callback?.({ text: `Error: ${error.message}` }); + return false; + } + }, + validate: async (_runtime: IAgentRuntime, message: Memory) => { + const symbols = extractSymbols( + message.content.text, + SYMBOL_SEARCH_MODE + ); + return symbols.length > 0; + }, + examples: [ + [ + { + user: "user", + content: { + text: "Search for $SOL and $ETH", + action: "SEARCH_TOKENS", + }, + }, + { + user: "user", + content: { + text: "Find information about $BTC", + action: "TOKEN_SEARCH", + }, + }, + { + user: "user", + content: { + text: "Look up $WETH token", + action: "LOOKUP_TOKENS", + }, + }, + { + user: "user", + content: { + text: "Tell me about SOL", + action: "CHECK_TOKEN", + }, + }, + { + user: "user", + content: { + text: "Give me details on $ADA", + action: "TOKEN_DETAILS", + }, + }, + { + user: "user", + content: { + text: "What can you tell me about $DOGE?", + action: "TOKEN_INFO", + }, + }, + { + user: "user", + content: { + text: "I need a report on $XRP", + action: "TOKEN_REPORT", + }, + }, + { + user: "user", + content: { + text: "Analyze $BNB for me", + action: "TOKEN_ANALYSIS", + }, + }, + { + user: "user", + content: { + text: "Overview of $LTC", + action: "TOKEN_OVERVIEW", + }, + }, + ], + ] as ActionExample[][], +} as Action; + +const formatTokenSummary = ( + symbol: string, + index: number, + tokens: TokenResult[] +) => { + return tokens + .map((token) => { + let output = `Search Result ${tokens.length > 0 ? index + 1 : ""} for ${symbol}:\n`; + output += `šŸ”– Symbol: $${token.symbol.toUpperCase()}\n`; + output += `šŸ”— Address: ${token.address}\n`; + output += `šŸŒ Network: ${token.network.toUpperCase()}\n`; + output += `šŸ’µ Price: ${formatPrice(token.price)} (${formatPercentChange(token.price_change_24h_percent)})\n`; + output += `šŸ’ø Volume (24h USD): ${formatValue(token.volume_24h_usd)}\n`; + output += token.market_cap + ? `šŸ’° Market Cap: ${formatValue(token.market_cap)}\n` + : ""; + output += token.fdv ? `šŸŒŠ FDV: ${formatValue(token.fdv)}\n` : ""; + return output; + }) + .join("\n"); +}; diff --git a/packages/plugin-birdeye/src/index.ts b/packages/plugin-birdeye/src/index.ts index 14fd2b2b71..9ec670ee3e 100644 --- a/packages/plugin-birdeye/src/index.ts +++ b/packages/plugin-birdeye/src/index.ts @@ -1,14 +1,16 @@ import { Plugin } from "@elizaos/core"; import { getWalletInfoAction } from "./actions/get-wallet-info"; -import { getTokenInfoAction } from "./actions/search-token-info"; +import { tokenSearchAddressAction } from "./actions/token-search-address"; +import { tokenSearchSymbolAction } from "./actions/token-search-symbol"; import { agentPortfolioProvider } from "./providers/agent-portfolio-provider"; export const birdeyePlugin: Plugin = { name: "birdeye", description: "Birdeye Plugin for token data and analytics", actions: [ - getTokenInfoAction, + tokenSearchSymbolAction, getWalletInfoAction, + tokenSearchAddressAction, // testAllEndpointsAction, // this action can be used to optionally test all endpoints ], evaluators: [], diff --git a/packages/plugin-birdeye/src/utils.ts b/packages/plugin-birdeye/src/utils.ts index 979d490508..024c525802 100644 --- a/packages/plugin-birdeye/src/utils.ts +++ b/packages/plugin-birdeye/src/utils.ts @@ -121,7 +121,7 @@ export const extractChain = (text: string): BirdeyeSupportedChain => { return "solana"; }; -export const extractAddressesFromString = (text: string): BaseAddress[] => { +export const extractAddresses = (text: string): BaseAddress[] => { const addresses: BaseAddress[] = []; // EVM-compatible chains (Ethereum, Arbitrum, Avalanche, BSC, Optimism, Polygon, Base, zkSync) @@ -300,7 +300,7 @@ export const formatValue = (value?: number): string => { export const formatPercentChange = (change?: number): string => { if (change === undefined) return "N/A"; - const symbol = change >= 0 ? "šŸ“ˆ" : "šŸ“‰"; + const symbol = change >= 0 ? "ā†‘" : "ā†“"; return `${symbol} ${Math.abs(change).toFixed(2)}%`; }; From e299a6a4e403aafa247d88bc9781f1a5a56af33a Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Sat, 4 Jan 2025 07:12:42 -0800 Subject: [PATCH 24/33] feat: add wallet search provider --- .../src/actions/get-wallet-info.ts | 175 ----------------- .../src/actions/token-search-address.ts | 50 ++--- .../src/actions/wallet-search-address.ts | 176 ++++++++++++++++++ packages/plugin-birdeye/src/index.ts | 4 +- 4 files changed, 189 insertions(+), 216 deletions(-) delete mode 100644 packages/plugin-birdeye/src/actions/get-wallet-info.ts create mode 100644 packages/plugin-birdeye/src/actions/wallet-search-address.ts diff --git a/packages/plugin-birdeye/src/actions/get-wallet-info.ts b/packages/plugin-birdeye/src/actions/get-wallet-info.ts deleted file mode 100644 index d11b58c4e8..0000000000 --- a/packages/plugin-birdeye/src/actions/get-wallet-info.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { - Action, - ActionExample, - composeContext, - elizaLogger, - generateText, - ModelClass, - type IAgentRuntime, - type Memory, - type State, -} from "@elizaos/core"; -import { BirdeyeProvider } from "../birdeye"; -import { extractAddresses } from "../utils"; - -const extractWalletAddressTemplate = `Given the recent message below: -{{recentMessages}} -Extract all wallet addresses mentioned in the message. Look for: -- Ethereum-style addresses (0x...) -- Solana-style addresses (base58 encoded) -- Any addresses prefixed with "wallet" or "address" - -For each address found, determine the chain (ethereum, solana) based on format. -Respond with a JSON array of objects containing the address and chain, no extra description needed. -Example: -Message: "Check wallet 0x742d35Cc6634C0532925a3b844Bc454e4438f44e and 5oGQxNmx4VrNHrq3mkZvpBzwTjnHHzxwpP1kPaS6cNyu" -Response: [ - {"address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "chain": "ethereum"}, - {"address": "5oGQxNmx4VrNHrq3mkZvpBzwTjnHHzxwpP1kPaS6cNyu", "chain": "solana"} -]`; - -const extractWalletAddressesFromMessage = async ( - runtime: IAgentRuntime, - message: Memory, - state: State -) => { - const context = composeContext({ - state, - template: extractWalletAddressTemplate, - }); - - const response = await generateText({ - runtime, - context, - modelClass: ModelClass.LARGE, - }); - - try { - const regex = new RegExp(/\[(.+)\]/gms); - const normalized = response && regex.exec(response)?.[0]; - return normalized ? JSON.parse(normalized) : []; - } catch { - return []; - } -}; - -export const getWalletInfoAction = { - name: "GET_WALLET_INFO", - similes: ["CHECK_WALLET", "WALLET_HOLDINGS", "PORTFOLIO_CHECK"], - description: "Check wallet portfolio and holdings information", - handler: async ( - runtime: IAgentRuntime, - message: Memory, - state: State, - _options: any, - callback?: any - ) => { - try { - const provider = new BirdeyeProvider(runtime.cacheManager); - - // Use the new extraction method - const addresses = await extractWalletAddressesFromMessage( - runtime, - message, - state - ); - - if (addresses.length === 0) { - callback?.({ - text: "No wallet addresses found in the message", - }); - return true; - } - - elizaLogger.info( - `Searching Birdeye provider for ${addresses.length} wallet addresses` - ); - - // Search Birdeye services for wallet portfolio data - const searchAddressesForTokenMatch = addresses.map((address) => - provider.fetchWalletPortfolio( - { - wallet: address.address, - }, - { - headers: { - chain: address.chain, - }, - } - ) - ); - - const results = await Promise.all(searchAddressesForTokenMatch); - const validResults = results.filter((r) => r !== null); - - elizaLogger.info( - `Found ${validResults.length} valid results for ${addresses.length} addresses` - ); - - if (validResults.length === 0) { - callback?.({ - text: "No portfolio data found for the provided addresses", - }); - return true; - } - - // Format portfolio data into readable text - const portfolioText = validResults - .map((wallet, index) => { - const tokens = wallet.data.items.slice(0, 5) || []; - const totalValue = tokens.reduce( - (sum, token) => sum + (token.valueUsd || 0), - 0 - ); - - const header = `*Wallet ${addresses[index].address}*\nTotal Value: $${totalValue.toLocaleString()}\n\nTop Holdings:`; - const tokenList = tokens - .map( - (token) => - `ā€¢ ${token.symbol}: $${token.valueUsd?.toLocaleString()} (${token.uiAmount?.toFixed(4)} tokens)` - ) - .join("\n"); - - return `${header}\n${tokenList}`; - }) - .join("\n\n"); - - callback?.({ text: portfolioText }); - return true; - } catch (error) { - console.error("Error in walletPortfolio handler:", error.message); - callback?.({ text: `Error: ${error.message}` }); - return false; - } - }, - validate: async (_runtime: IAgentRuntime, message: Memory) => { - // Check if the message contains any potential wallet addresses - const addresses = extractAddresses(message.content.text); - return addresses.length > 0; - }, - examples: [ - [ - { - user: "user", - content: { - text: "Show me the portfolio for 0x1234...5678", - action: "WALLET_PORTFOLIO", - }, - }, - { - user: "user", - content: { - text: "What tokens does wallet 0xabcd...efgh hold?", - action: "CHECK_WALLET", - }, - }, - { - user: "user", - content: { - text: "Check holdings in 0x9876...5432", - action: "WALLET_HOLDINGS", - }, - }, - ], - ] as ActionExample[][], -} as Action; diff --git a/packages/plugin-birdeye/src/actions/token-search-address.ts b/packages/plugin-birdeye/src/actions/token-search-address.ts index 8211228667..1c50a831ac 100644 --- a/packages/plugin-birdeye/src/actions/token-search-address.ts +++ b/packages/plugin-birdeye/src/actions/token-search-address.ts @@ -148,7 +148,7 @@ export const tokenSearchAddressAction = { console.log(results); - const completeResults = `I performed a search for the token symbols you requested and found the following results (for more details search by contract address):\n\n${results + const completeResults = `I performed a search for the token addresses you requested and found the following results:\n\n${results .map( (result, i) => `${formatTokenReport(addresses[i], i, result)}` @@ -172,64 +172,36 @@ export const tokenSearchAddressAction = { { user: "user", content: { - text: "Search for $SOL and $ETH", - action: "SEARCH_TOKENS", + text: "Search for 0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9", + action: "TOKEN_SEARCH_ADDRESS", }, }, { user: "user", content: { - text: "Find information about $BTC", - action: "TOKEN_SEARCH", + text: "Look up contract So11111111111111111111111111111111111111112", + action: "TOKEN_ADDRESS_LOOKUP", }, }, { user: "user", content: { - text: "Look up $WETH token", - action: "LOOKUP_TOKENS", + text: "Check this address: 0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + action: "CHECK_TOKEN_ADDRESS", }, }, { user: "user", content: { - text: "Tell me about SOL", - action: "CHECK_TOKEN", + text: "Get info for 0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", + action: "TOKEN_ADDRESS_INFO", }, }, { user: "user", content: { - text: "Give me details on $ADA", - action: "TOKEN_DETAILS", - }, - }, - { - user: "user", - content: { - text: "What can you tell me about $DOGE?", - action: "TOKEN_INFO", - }, - }, - { - user: "user", - content: { - text: "I need a report on $XRP", - action: "TOKEN_REPORT", - }, - }, - { - user: "user", - content: { - text: "Analyze $BNB for me", - action: "TOKEN_ANALYSIS", - }, - }, - { - user: "user", - content: { - text: "Overview of $LTC", - action: "TOKEN_OVERVIEW", + text: "Analyze contract 0x514910771af9ca656af840dff83e8264ecf986ca", + action: "TOKEN_CONTRACT_SEARCH", }, }, ], diff --git a/packages/plugin-birdeye/src/actions/wallet-search-address.ts b/packages/plugin-birdeye/src/actions/wallet-search-address.ts new file mode 100644 index 0000000000..72c686bfe6 --- /dev/null +++ b/packages/plugin-birdeye/src/actions/wallet-search-address.ts @@ -0,0 +1,176 @@ +import { + Action, + ActionExample, + elizaLogger, + IAgentRuntime, + Memory, + State, +} from "@elizaos/core"; +import { BirdeyeProvider } from "../birdeye"; +import { WalletPortfolioResponse } from "../types/api/wallet"; +import { BaseAddress } from "../types/shared"; +import { extractAddresses } from "../utils"; + +export const walletSearchAddressAction = { + name: "WALLET_SEARCH_ADDRESS", + similes: [ + "SEARCH_WALLET_ADDRESS", + "FIND_WALLET_ADDRESS", + "LOOKUP_WALLET_ADDRESS", + "CHECK_WALLET_ADDRESS", + "GET_WALLET_BY_ADDRESS", + "WALLET_ADDRESS_INFO", + "WALLET_ADDRESS_LOOKUP", + "WALLET_ADDRESS_SEARCH", + "WALLET_ADDRESS_CHECK", + "WALLET_ADDRESS_DETAILS", + "WALLET_CONTRACT_SEARCH", + "WALLET_CONTRACT_LOOKUP", + "WALLET_CONTRACT_INFO", + "WALLET_CONTRACT_CHECK", + "VERIFY_WALLET_ADDRESS", + "VALIDATE_WALLET_ADDRESS", + "GET_WALLET_INFO", + "WALLET_INFO", + "WALLET_REPORT", + "WALLET_ANALYSIS", + "WALLET_OVERVIEW", + "WALLET_SUMMARY", + "WALLET_INSIGHT", + "WALLET_DATA", + "WALLET_STATS", + "WALLET_METRICS", + "WALLET_PROFILE", + "WALLET_REVIEW", + "WALLET_CHECK", + "WALLET_LOOKUP", + "WALLET_FIND", + "WALLET_DISCOVER", + "WALLET_EXPLORE", + ], + description: + "Search for detailed wallet information including portfolio and transaction data by address", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: any, + callback?: any + ) => { + try { + const provider = new BirdeyeProvider(runtime.cacheManager); + + // get all wallet addresses from the message + const addresses = extractAddresses(message.content.text); + + elizaLogger.info( + `Searching Birdeye provider for ${addresses.length} addresses` + ); + + // for each symbol, do a search in Birdeye. This will return a list of token results that may be amatch to the token symbol. + const results: WalletPortfolioResponse[] = await Promise.all( + addresses.map(async ({ address, chain: addressChain }) => { + // address detection can't distinguish between evm chains, so we currently only do address search on ETH for EVM addresses. Future support will be added for other chains if the user requests it. + const chain = + addressChain === "evm" ? "ethereum" : addressChain; + return provider.fetchWalletPortfolio( + { + wallet: address, + }, + { + headers: { + chain: chain, + }, + } + ); + }) + ); + + console.log(results); + + const completeResults = `I performed a search for the wallet addresses you requested and found the following results:\n\n${results + .map( + (result, i) => + `${formatWalletReport(addresses[i], results.length, i, result)}` + ) + .join("\n\n")}`; + + callback?.({ text: completeResults }); + return true; + } catch (error) { + console.error("Error in searchTokens handler:", error.message); + callback?.({ text: `Error: ${error.message}` }); + return false; + } + }, + validate: async (_runtime: IAgentRuntime, message: Memory) => { + const addresses = extractAddresses(message.content.text); + return addresses.length > 0; + }, + examples: [ + [ + { + user: "user", + content: { + text: "Search wallet 0x1234567890abcdef1234567890abcdef12345678", + action: "WALLET_SEARCH_ADDRESS", + }, + }, + { + user: "user", + content: { + text: "Look up wallet address HN7cABqLq46Es1jh92dQQisAq662SmxELLLsHHe4YWrH", + action: "WALLET_ADDRESS_LOOKUP", + }, + }, + { + user: "user", + content: { + text: "Check this address: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", + action: "CHECK_WALLET_ADDRESS", + }, + }, + { + user: "user", + content: { + text: "Get wallet info for 5yBYpGQRHPz4i5FkVnP9h9VTJBMnwgHRe5L5gw2bwp9q", + action: "WALLET_INFO", + }, + }, + { + user: "user", + content: { + text: "Show me portfolio for 0x3cD751E6b0078Be393132286c442345e5DC49699", + action: "WALLET_OVERVIEW", + }, + }, + ], + ] as ActionExample[][], +} as Action; + +// take all the details of the results and present to the user +const formatWalletReport = ( + address: BaseAddress, + totalResults: number, + index: number, + result: WalletPortfolioResponse +) => { + const tokens = result.data.items.slice(0, 10) || []; + const totalValue = tokens.reduce( + (sum, token) => sum + (token.valueUsd || 0), + 0 + ); + + let header = `Wallet Result ${totalResults > 1 ? `#${index + 1}` : ""}`; + header += `šŸ‘› Address ${address.address}*\n`; + header += `šŸ’° Total Value: $${totalValue.toLocaleString()}\n\n`; + header += `šŸ”– Top Holdings:`; + const tokenList = tokens + .map( + (token) => + `ā€¢ $${token.symbol}: $${token.valueUsd?.toLocaleString()} (${token.uiAmount?.toFixed(4)} tokens)` + ) + .join("\n"); + + return `${header}\n${tokenList}`; +}; diff --git a/packages/plugin-birdeye/src/index.ts b/packages/plugin-birdeye/src/index.ts index 9ec670ee3e..1598352022 100644 --- a/packages/plugin-birdeye/src/index.ts +++ b/packages/plugin-birdeye/src/index.ts @@ -1,7 +1,7 @@ import { Plugin } from "@elizaos/core"; -import { getWalletInfoAction } from "./actions/get-wallet-info"; import { tokenSearchAddressAction } from "./actions/token-search-address"; import { tokenSearchSymbolAction } from "./actions/token-search-symbol"; +import { walletSearchAddressAction } from "./actions/wallet-search-address"; import { agentPortfolioProvider } from "./providers/agent-portfolio-provider"; export const birdeyePlugin: Plugin = { @@ -9,8 +9,8 @@ export const birdeyePlugin: Plugin = { description: "Birdeye Plugin for token data and analytics", actions: [ tokenSearchSymbolAction, - getWalletInfoAction, tokenSearchAddressAction, + walletSearchAddressAction, // testAllEndpointsAction, // this action can be used to optionally test all endpoints ], evaluators: [], From a4d3f7424041a6a3d12f5d8799008889d85d2605 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Sat, 4 Jan 2025 07:13:16 -0800 Subject: [PATCH 25/33] chore: cleanup formatting --- packages/plugin-birdeye/src/actions/token-search-address.ts | 2 +- packages/plugin-birdeye/src/actions/wallet-search-address.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugin-birdeye/src/actions/token-search-address.ts b/packages/plugin-birdeye/src/actions/token-search-address.ts index 1c50a831ac..b07cb51a66 100644 --- a/packages/plugin-birdeye/src/actions/token-search-address.ts +++ b/packages/plugin-birdeye/src/actions/token-search-address.ts @@ -220,7 +220,7 @@ const formatTokenReport = ( output += `\n`; output += `Token Overview:\n`; output += `šŸ“ Name: ${result.overview.data.name}\n`; - output += `šŸ”– Symbol: ${result.overview.data.symbol}\n`; + output += `šŸ”– Symbol: ${result.overview.data.symbol.toUpperCase()}\n`; output += `šŸ”— Address: ${address.address}\n`; output += `šŸ”¢ Decimals: ${result.overview.data.decimals}\n`; output += ``; diff --git a/packages/plugin-birdeye/src/actions/wallet-search-address.ts b/packages/plugin-birdeye/src/actions/wallet-search-address.ts index 72c686bfe6..585d8480b8 100644 --- a/packages/plugin-birdeye/src/actions/wallet-search-address.ts +++ b/packages/plugin-birdeye/src/actions/wallet-search-address.ts @@ -168,7 +168,7 @@ const formatWalletReport = ( const tokenList = tokens .map( (token) => - `ā€¢ $${token.symbol}: $${token.valueUsd?.toLocaleString()} (${token.uiAmount?.toFixed(4)} tokens)` + `ā€¢ $${token.symbol.toUpperCase()}: $${token.valueUsd?.toLocaleString()} (${token.uiAmount?.toFixed(4)} tokens)` ) .join("\n"); From 19e04c1c594d187d92bb5015c3b4c16f516c7eb3 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Sat, 4 Jan 2025 07:29:45 -0800 Subject: [PATCH 26/33] chore: cleanup format --- .../plugin-birdeye/src/actions/token-search-symbol.ts | 10 +++++++--- .../src/actions/wallet-search-address.ts | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/plugin-birdeye/src/actions/token-search-symbol.ts b/packages/plugin-birdeye/src/actions/token-search-symbol.ts index f6923f651c..bcf7e33c6d 100644 --- a/packages/plugin-birdeye/src/actions/token-search-symbol.ts +++ b/packages/plugin-birdeye/src/actions/token-search-symbol.ts @@ -112,7 +112,7 @@ export const tokenSearchSymbolAction = { (result, i) => `${formatTokenSummary(symbols[i], i, result)}` ) - .join("\n\n")}`; + .join("\n")}`; callback?.({ text: completeResults }); return true; @@ -204,8 +204,12 @@ const formatTokenSummary = ( tokens: TokenResult[] ) => { return tokens - .map((token) => { - let output = `Search Result ${tokens.length > 0 ? index + 1 : ""} for ${symbol}:\n`; + .map((token, i) => { + let output = ``; + if (i === 0) { + output += `Search Results for ${symbol}:\n\n`; + } + output += `Search Result #${tokens.length > 0 ? i + 1 : ""}:\n`; output += `šŸ”– Symbol: $${token.symbol.toUpperCase()}\n`; output += `šŸ”— Address: ${token.address}\n`; output += `šŸŒ Network: ${token.network.toUpperCase()}\n`; diff --git a/packages/plugin-birdeye/src/actions/wallet-search-address.ts b/packages/plugin-birdeye/src/actions/wallet-search-address.ts index 585d8480b8..1ec2fc20ee 100644 --- a/packages/plugin-birdeye/src/actions/wallet-search-address.ts +++ b/packages/plugin-birdeye/src/actions/wallet-search-address.ts @@ -161,9 +161,9 @@ const formatWalletReport = ( 0 ); - let header = `Wallet Result ${totalResults > 1 ? `#${index + 1}` : ""}`; + let header = `Wallet Result ${totalResults > 1 ? `#${index + 1}` : ""}\n`; header += `šŸ‘› Address ${address.address}*\n`; - header += `šŸ’° Total Value: $${totalValue.toLocaleString()}\n\n`; + header += `šŸ’° Total Value: $${totalValue.toLocaleString()}\n`; header += `šŸ”– Top Holdings:`; const tokenList = tokens .map( From 7e5d2b963754de24c668b4e520e6dc87654d050a Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Sat, 4 Jan 2025 07:40:58 -0800 Subject: [PATCH 27/33] chore: remove changes to chat --- client/src/Chat.tsx | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/client/src/Chat.tsx b/client/src/Chat.tsx index e1b8d1c71e..39ee2cb435 100644 --- a/client/src/Chat.tsx +++ b/client/src/Chat.tsx @@ -2,7 +2,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { useMutation } from "@tanstack/react-query"; import { ImageIcon } from "lucide-react"; -import { useEffect, useRef, useState } from "react"; +import { useRef, useState } from "react"; import { useParams } from "react-router-dom"; import "./App.css"; @@ -18,15 +18,6 @@ export default function Chat() { const [messages, setMessages] = useState([]); const [selectedFile, setSelectedFile] = useState(null); const fileInputRef = useRef(null); - const messagesEndRef = useRef(null); - - const scrollToBottom = () => { - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }; - - useEffect(() => { - scrollToBottom(); - }, [messages]); const mutation = useMutation({ mutationFn: async (text: string) => { @@ -92,8 +83,8 @@ export default function Chat() { : "justify-start" }`} > -
                                         )
                                     ))}
-                                
+ )) ) : ( @@ -123,7 +114,6 @@ export default function Chat() { No messages yet. Start a conversation! )} -
From 8689ea3e16fb136b0077da195cde705e6861bb0d Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Sat, 4 Jan 2025 07:42:08 -0800 Subject: [PATCH 28/33] chore: revert chat --- client/src/Chat.tsx | 81 ++++++++------------------------------------- 1 file changed, 14 insertions(+), 67 deletions(-) diff --git a/client/src/Chat.tsx b/client/src/Chat.tsx index 39ee2cb435..f077935167 100644 --- a/client/src/Chat.tsx +++ b/client/src/Chat.tsx @@ -1,56 +1,48 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { useMutation } from "@tanstack/react-query"; -import { ImageIcon } from "lucide-react"; -import { useRef, useState } from "react"; +import { useState } from "react"; import { useParams } from "react-router-dom"; import "./App.css"; type TextResponse = { text: string; user: string; - attachments?: { url: string; contentType: string; title: string }[]; }; export default function Chat() { const { agentId } = useParams(); const [input, setInput] = useState(""); const [messages, setMessages] = useState([]); - const [selectedFile, setSelectedFile] = useState(null); - const fileInputRef = useRef(null); const mutation = useMutation({ mutationFn: async (text: string) => { - const formData = new FormData(); - formData.append("text", text); - formData.append("userId", "user"); - formData.append("roomId", `default-room-${agentId}`); - - if (selectedFile) { - formData.append("file", selectedFile); - } - const res = await fetch(`/api/${agentId}/message`, { method: "POST", - body: formData, + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + text, + userId: "user", + roomId: `default-room-${agentId}`, + }), }); return res.json() as Promise; }, onSuccess: (data) => { setMessages((prev) => [...prev, ...data]); - setSelectedFile(null); }, }); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if (!input.trim() && !selectedFile) return; + if (!input.trim()) return; // Add user message immediately to state const userMessage: TextResponse = { text: input, user: "user", - attachments: selectedFile ? [{ url: URL.createObjectURL(selectedFile), contentType: selectedFile.type, title: selectedFile.name }] : undefined, }; setMessages((prev) => [...prev, userMessage]); @@ -58,17 +50,6 @@ export default function Chat() { setInput(""); }; - const handleFileSelect = () => { - fileInputRef.current?.click(); - }; - - const handleFileChange = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (file && file.type.startsWith('image/')) { - setSelectedFile(file); - } - }; - return (
@@ -90,23 +71,10 @@ export default function Chat() { : "bg-muted" }`} > - {message.text} - {message.attachments?.map((attachment, i) => ( - attachment.contentType.startsWith('image/') && ( - {attachment.title - ) - ))} -
+
+                                        {message.text}
+                                    
+
)) ) : ( @@ -120,13 +88,6 @@ export default function Chat() {
- setInput(e.target.value)} @@ -134,24 +95,10 @@ export default function Chat() { className="flex-1" disabled={mutation.isPending} /> -
- {selectedFile && ( -
- Selected file: {selectedFile.name} -
- )}
From 1748a09d0e19412935753756ba706e2d338351b3 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Sat, 4 Jan 2025 07:42:49 -0800 Subject: [PATCH 29/33] chore: revert chat --- client/src/Chat.tsx | 112 +++++++++++++++++++++++++++++--------------- 1 file changed, 74 insertions(+), 38 deletions(-) diff --git a/client/src/Chat.tsx b/client/src/Chat.tsx index f077935167..f9facc1259 100644 --- a/client/src/Chat.tsx +++ b/client/src/Chat.tsx @@ -1,55 +1,56 @@ +import type { TextResponse } from "@/api"; +import { useSendMessageMutation } from "@/api"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { useMutation } from "@tanstack/react-query"; -import { useState } from "react"; +import { ImageIcon } from "lucide-react"; +import { useEffect, useRef, useState } from "react"; import { useParams } from "react-router-dom"; import "./App.css"; -type TextResponse = { - text: string; - user: string; -}; - export default function Chat() { const { agentId } = useParams(); const [input, setInput] = useState(""); const [messages, setMessages] = useState([]); + const [selectedFile, setSelectedFile] = useState(null); + const fileInputRef = useRef(null); + const messagesEndRef = useRef(null); + const { mutate: sendMessage, isPending } = useSendMessageMutation({ setMessages, setSelectedFile }); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }; - const mutation = useMutation({ - mutationFn: async (text: string) => { - const res = await fetch(`/api/${agentId}/message`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - text, - userId: "user", - roomId: `default-room-${agentId}`, - }), - }); - return res.json() as Promise; - }, - onSuccess: (data) => { - setMessages((prev) => [...prev, ...data]); - }, - }); + useEffect(() => { + scrollToBottom(); + }, [messages]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if (!input.trim()) return; + if ((!input.trim() && !selectedFile) || !agentId) return; // Add user message immediately to state const userMessage: TextResponse = { text: input, user: "user", + attachments: selectedFile ? [{ url: URL.createObjectURL(selectedFile), contentType: selectedFile.type, title: selectedFile.name }] : undefined, }; setMessages((prev) => [...prev, userMessage]); - mutation.mutate(input); + sendMessage({ text: input, agentId, selectedFile }); setInput(""); }; + const handleFileSelect = () => { + fileInputRef.current?.click(); + }; + + const handleFileChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file && file.type.startsWith('image/')) { + setSelectedFile(file); + } + }; + return (
@@ -64,17 +65,30 @@ export default function Chat() { : "justify-start" }`} > -
-
-                                        {message.text}
-                                    
-
+ {message.text} + {message.attachments?.map((attachment, i) => ( + attachment.contentType.startsWith('image/') && ( + {attachment.title + ) + ))} +
)) ) : ( @@ -82,25 +96,47 @@ export default function Chat() { No messages yet. Start a conversation!
)} +
+ setInput(e.target.value)} placeholder="Type a message..." className="flex-1" - disabled={mutation.isPending} + disabled={isPending} /> - +
+ {selectedFile && ( +
+ Selected file: {selectedFile.name} +
+ )}
); -} +} \ No newline at end of file From da48e6fce540d9fcff24b99e9323822b3767bb9a Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Sat, 4 Jan 2025 07:43:49 -0800 Subject: [PATCH 30/33] chore: fix chat --- client/src/Chat.tsx | 61 +++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/client/src/Chat.tsx b/client/src/Chat.tsx index f9facc1259..39ee2cb435 100644 --- a/client/src/Chat.tsx +++ b/client/src/Chat.tsx @@ -1,32 +1,50 @@ -import type { TextResponse } from "@/api"; -import { useSendMessageMutation } from "@/api"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { useMutation } from "@tanstack/react-query"; import { ImageIcon } from "lucide-react"; -import { useEffect, useRef, useState } from "react"; +import { useRef, useState } from "react"; import { useParams } from "react-router-dom"; import "./App.css"; +type TextResponse = { + text: string; + user: string; + attachments?: { url: string; contentType: string; title: string }[]; +}; + export default function Chat() { const { agentId } = useParams(); const [input, setInput] = useState(""); const [messages, setMessages] = useState([]); const [selectedFile, setSelectedFile] = useState(null); const fileInputRef = useRef(null); - const messagesEndRef = useRef(null); - const { mutate: sendMessage, isPending } = useSendMessageMutation({ setMessages, setSelectedFile }); - const scrollToBottom = () => { - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }; + const mutation = useMutation({ + mutationFn: async (text: string) => { + const formData = new FormData(); + formData.append("text", text); + formData.append("userId", "user"); + formData.append("roomId", `default-room-${agentId}`); + + if (selectedFile) { + formData.append("file", selectedFile); + } - useEffect(() => { - scrollToBottom(); - }, [messages]); + const res = await fetch(`/api/${agentId}/message`, { + method: "POST", + body: formData, + }); + return res.json() as Promise; + }, + onSuccess: (data) => { + setMessages((prev) => [...prev, ...data]); + setSelectedFile(null); + }, + }); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if ((!input.trim() && !selectedFile) || !agentId) return; + if (!input.trim() && !selectedFile) return; // Add user message immediately to state const userMessage: TextResponse = { @@ -36,7 +54,7 @@ export default function Chat() { }; setMessages((prev) => [...prev, userMessage]); - sendMessage({ text: input, agentId, selectedFile }); + mutation.mutate(input); setInput(""); }; @@ -65,8 +83,8 @@ export default function Chat() { : "justify-start" }`} > -
                                         )
                                     ))}
-                                 
+ )) ) : ( @@ -96,7 +114,6 @@ export default function Chat() { No messages yet. Start a conversation! )} -
@@ -115,19 +132,19 @@ export default function Chat() { onChange={(e) => setInput(e.target.value)} placeholder="Type a message..." className="flex-1" - disabled={isPending} + disabled={mutation.isPending} /> - {selectedFile && ( @@ -139,4 +156,4 @@ export default function Chat() { ); -} \ No newline at end of file +} From f75d910e7a4e707a92f849176c3c3878481d8cb7 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Sat, 4 Jan 2025 07:45:20 -0800 Subject: [PATCH 31/33] chore: fix chat --- client/src/Chat.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/client/src/Chat.tsx b/client/src/Chat.tsx index 39ee2cb435..f9538d1323 100644 --- a/client/src/Chat.tsx +++ b/client/src/Chat.tsx @@ -2,7 +2,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { useMutation } from "@tanstack/react-query"; import { ImageIcon } from "lucide-react"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useParams } from "react-router-dom"; import "./App.css"; @@ -18,6 +18,15 @@ export default function Chat() { const [messages, setMessages] = useState([]); const [selectedFile, setSelectedFile] = useState(null); const fileInputRef = useRef(null); + const messagesEndRef = useRef(null); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); const mutation = useMutation({ mutationFn: async (text: string) => { @@ -114,6 +123,7 @@ export default function Chat() { No messages yet. Start a conversation! )} +
From e60514e48ca53d92f46aeb3ae79a33341667e521 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Wed, 8 Jan 2025 07:00:20 -0800 Subject: [PATCH 32/33] chore: update readme --- packages/plugin-birdeye/README.md | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/plugin-birdeye/README.md b/packages/plugin-birdeye/README.md index 9b0d146780..0ac543844f 100644 --- a/packages/plugin-birdeye/README.md +++ b/packages/plugin-birdeye/README.md @@ -6,29 +6,24 @@ A powerful plugin for Eliza that integrates with Birdeye's comprehensive DeFi an ### Provider Featurs -- **Wallet Portfolio Provider** +- **Agent Portfolio Provider** - - If `BIRDEYE_WALLET_ADDR` is set, this provider will fetch the wallet's portfolio data from Birdeye and be able to respond to questions + - If `BIRDEYE_WALLET_ADDR` is set, this provider will fetch the wallet's portfolio data from Birdeye and be able to respond to questions related to the wallet's holdings. -- **Wallet Search Provider** - - - If the user mentions a wallet address, this provider will search for the address in Birdeye and be able to provide information about the wallet. This includes support for multiple addresses in the same message. - -- **Symbol Search Provider** +### Action Features - - If the user mentions a token symbol such as $SOL, $ETH or any random token symbol, this provider will search for the symbol in Birdeye and be able to provide information about the token. This includes support for multiple symbols in the same message. - - i.e. "Tell me about $SOL and $PEPE" +- **Token Search Address** -- **Address Search Provider** + - This action will search input message for token addresses and when present will query Birdeye for token information - - If the user mentions a token address, this provider will search for the address in Birdeye and be able to provide information about the token. This includes support for multiple addresses in the same message. - - i.e. "Tell me about 0x1234567890 and 0x9876543210" +- **Token Search Symbol** -### Action Features + - This action will search input message for token symbols in the format of `$SYMBOL` and when present will query Birdeye for token information. Note that this action currently only supports SOL, SUI, and ETH addresses. + - _Any addresses that look like EVM addresses will be treated as ETH addresses since there is no easy way to distinguish between the other EVM chains that are supported by Birdeye_. -- **Report Token** +- **Wallet Search Address** - - This action will report on the current details of the wallet specified in the `BIRDEYE_WALLET_ADDR` setting. + - This action will search input message for wallet addresses and when present will query Birdeye for wallet information ## API Reference From bf12b5e9e7e1bfcfd165a3d7b682e5be84bb28d6 Mon Sep 17 00:00:00 2001 From: "J. Brandon Johnson" Date: Wed, 8 Jan 2025 07:48:46 -0800 Subject: [PATCH 33/33] chore: fix bug with toUpperCase() --- packages/plugin-birdeye/src/actions/token-search-address.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/plugin-birdeye/src/actions/token-search-address.ts b/packages/plugin-birdeye/src/actions/token-search-address.ts index b07cb51a66..783e5ae06d 100644 --- a/packages/plugin-birdeye/src/actions/token-search-address.ts +++ b/packages/plugin-birdeye/src/actions/token-search-address.ts @@ -220,7 +220,9 @@ const formatTokenReport = ( output += `\n`; output += `Token Overview:\n`; output += `šŸ“ Name: ${result.overview.data.name}\n`; - output += `šŸ”– Symbol: ${result.overview.data.symbol.toUpperCase()}\n`; + output += result.overview.data.symbol + ? `šŸ”– Symbol: ${result.overview.data.symbol.toUpperCase()}\n` + : ""; output += `šŸ”— Address: ${address.address}\n`; output += `šŸ”¢ Decimals: ${result.overview.data.decimals}\n`; output += ``;