From 572239ebe753f08815d648e37211f6f72557864f Mon Sep 17 00:00:00 2001 From: xiaohuo Date: Sun, 22 Dec 2024 19:58:43 +0800 Subject: [PATCH 1/7] feat: init plugin for solana agentkit --- agent/package.json | 1 + agent/src/index.ts | 4 + packages/plugin-solana-agentkit/.npmignore | 6 + .../plugin-solana-agentkit/eslint.config.mjs | 3 + packages/plugin-solana-agentkit/package.json | 34 + .../src/actions/createToken.ts | 73 ++ .../src/actions/transfer.ts | 263 ++++ .../plugin-solana-agentkit/src/bignumber.ts | 9 + .../plugin-solana-agentkit/src/environment.ts | 76 ++ .../src/evaluators/trust.ts | 543 ++++++++ packages/plugin-solana-agentkit/src/index.ts | 24 + .../src/keypairUtils.ts | 82 ++ .../src/providers/orderBook.ts | 45 + .../src/providers/simulationSellingService.ts | 501 ++++++++ .../src/providers/token.ts | 1124 +++++++++++++++++ .../src/providers/tokenUtils.ts | 72 ++ .../src/providers/trustScoreProvider.ts | 740 +++++++++++ .../src/providers/wallet.ts | 391 ++++++ .../src/tests/token.test.ts | 134 ++ .../plugin-solana-agentkit/src/types/token.ts | 302 +++++ packages/plugin-solana-agentkit/tsconfig.json | 10 + .../plugin-solana-agentkit/tsup.config.ts | 29 + pnpm-lock.yaml | 639 +++++++++- 23 files changed, 5103 insertions(+), 2 deletions(-) create mode 100644 packages/plugin-solana-agentkit/.npmignore create mode 100644 packages/plugin-solana-agentkit/eslint.config.mjs create mode 100644 packages/plugin-solana-agentkit/package.json create mode 100644 packages/plugin-solana-agentkit/src/actions/createToken.ts create mode 100644 packages/plugin-solana-agentkit/src/actions/transfer.ts create mode 100644 packages/plugin-solana-agentkit/src/bignumber.ts create mode 100644 packages/plugin-solana-agentkit/src/environment.ts create mode 100644 packages/plugin-solana-agentkit/src/evaluators/trust.ts create mode 100644 packages/plugin-solana-agentkit/src/index.ts create mode 100644 packages/plugin-solana-agentkit/src/keypairUtils.ts create mode 100644 packages/plugin-solana-agentkit/src/providers/orderBook.ts create mode 100644 packages/plugin-solana-agentkit/src/providers/simulationSellingService.ts create mode 100644 packages/plugin-solana-agentkit/src/providers/token.ts create mode 100644 packages/plugin-solana-agentkit/src/providers/tokenUtils.ts create mode 100644 packages/plugin-solana-agentkit/src/providers/trustScoreProvider.ts create mode 100644 packages/plugin-solana-agentkit/src/providers/wallet.ts create mode 100644 packages/plugin-solana-agentkit/src/tests/token.test.ts create mode 100644 packages/plugin-solana-agentkit/src/types/token.ts create mode 100644 packages/plugin-solana-agentkit/tsconfig.json create mode 100644 packages/plugin-solana-agentkit/tsup.config.ts diff --git a/agent/package.json b/agent/package.json index be8a3e0e29..d8db86b23f 100644 --- a/agent/package.json +++ b/agent/package.json @@ -45,6 +45,7 @@ "@elizaos/plugin-nft-generation": "workspace:*", "@elizaos/plugin-node": "workspace:*", "@elizaos/plugin-solana": "workspace:*", + "@elizaos/plugin-solana-agentkit": "workspace:*", "@elizaos/plugin-starknet": "workspace:*", "@elizaos/plugin-ton": "workspace:*", "@elizaos/plugin-sui": "workspace:*", diff --git a/agent/src/index.ts b/agent/src/index.ts index b0ac9dbe48..fbe663f0d8 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -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 { solanaAgentkitPlguin } from "@elizaos/plugin-solana-agentkit"; import { suiPlugin } from "@elizaos/plugin-sui"; import { TEEMode, teePlugin } from "@elizaos/plugin-tee"; import { tonPlugin } from "@elizaos/plugin-ton"; @@ -486,6 +487,9 @@ export async function createAgent( !getSecret(character, "WALLET_PUBLIC_KEY")?.startsWith("0x")) ? solanaPlugin : null, + getSecret(character, "SOLANA_PRIVATE_KEY") + ? solanaAgentkitPlguin + : null, (getSecret(character, "NEAR_ADDRESS") || getSecret(character, "NEAR_WALLET_PUBLIC_KEY")) && getSecret(character, "NEAR_WALLET_SECRET_KEY") diff --git a/packages/plugin-solana-agentkit/.npmignore b/packages/plugin-solana-agentkit/.npmignore new file mode 100644 index 0000000000..078562ecea --- /dev/null +++ b/packages/plugin-solana-agentkit/.npmignore @@ -0,0 +1,6 @@ +* + +!dist/** +!package.json +!readme.md +!tsup.config.ts \ No newline at end of file diff --git a/packages/plugin-solana-agentkit/eslint.config.mjs b/packages/plugin-solana-agentkit/eslint.config.mjs new file mode 100644 index 0000000000..92fe5bbebe --- /dev/null +++ b/packages/plugin-solana-agentkit/eslint.config.mjs @@ -0,0 +1,3 @@ +import eslintGlobalConfig from "../../eslint.config.mjs"; + +export default [...eslintGlobalConfig]; diff --git a/packages/plugin-solana-agentkit/package.json b/packages/plugin-solana-agentkit/package.json new file mode 100644 index 0000000000..f2abfe7f8a --- /dev/null +++ b/packages/plugin-solana-agentkit/package.json @@ -0,0 +1,34 @@ +{ + "name": "@elizaos/plugin-solana-agentkit", + "version": "0.1.7-alpha.1", + "main": "dist/index.js", + "type": "module", + "types": "dist/index.d.ts", + "dependencies": { + "@coral-xyz/anchor": "0.30.1", + "@elizaos/core": "workspace:*", + "@elizaos/plugin-tee": "workspace:*", + "@elizaos/plugin-trustdb": "workspace:*", + "@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", + "solana-agent-kit": "^1.2.0", + "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" + } +} diff --git a/packages/plugin-solana-agentkit/src/actions/createToken.ts b/packages/plugin-solana-agentkit/src/actions/createToken.ts new file mode 100644 index 0000000000..74669e52fb --- /dev/null +++ b/packages/plugin-solana-agentkit/src/actions/createToken.ts @@ -0,0 +1,73 @@ +import { + ActionExample, + Content, + elizaLogger, + HandlerCallback, + IAgentRuntime, + Memory, + ModelClass, + State, + type Action, +} from "@elizaos/core"; + +export interface CreateTokenContent extends Content { + name: string; + uri: string; + symbol: string; + decimals: number; + initialSupply: number; +} + +function isCreateTokenContent( + runtime: IAgentRuntime, + content: any +): content is CreateTokenContent { + elizaLogger.log("Content for createToken", content); + return ( + typeof content.name === "string" && + typeof content.uri === "string" && + typeof content.symbol === "string" && + typeof content.decimals === "number" && + typeof content.initialSupply === "number" + ); +} + +export default { + name: "CREATE_TOKEN", + similes: ["DEPLOY_TOKEN"], + validate: async (runtime: IAgentRuntime, message: Memory) => true, + description: "Create tokens", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: { [key: string]: unknown }, + callback?: HandlerCallback + ): Promise => { + elizaLogger.log("Starting CREATE_TOKEN handler..."); + return true; + }, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Create token, name is Example Token, symbol is EXMPL, uri is https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/CompressedCoil/image.png, decimals is 9, initialSupply is 100000000000", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll creaete token now...", + action: "CREATE_TOKEN", + }, + }, + { + user: "{{user2}}", + content: { + text: "Successfully create token 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", + }, + }, + ], + ] as ActionExample[][], +} as Action; diff --git a/packages/plugin-solana-agentkit/src/actions/transfer.ts b/packages/plugin-solana-agentkit/src/actions/transfer.ts new file mode 100644 index 0000000000..118e2b2468 --- /dev/null +++ b/packages/plugin-solana-agentkit/src/actions/transfer.ts @@ -0,0 +1,263 @@ +import { + getAssociatedTokenAddressSync, + createTransferInstruction, +} from "@solana/spl-token"; +import { elizaLogger, settings } from "@elizaos/core"; + +import { + Connection, + PublicKey, + TransactionMessage, + VersionedTransaction, +} from "@solana/web3.js"; + +import { + ActionExample, + Content, + HandlerCallback, + IAgentRuntime, + Memory, + ModelClass, + State, + type Action, +} from "@elizaos/core"; +import { composeContext } from "@elizaos/core"; +import { getWalletKey } from "../keypairUtils"; +import { generateObjectDeprecated } from "@elizaos/core"; + +export interface TransferContent extends Content { + tokenAddress: string; + recipient: string; + amount: string | number; +} + +function isTransferContent( + runtime: IAgentRuntime, + content: any +): content is TransferContent { + console.log("Content for transfer", content); + return ( + typeof content.tokenAddress === "string" && + typeof content.recipient === "string" && + (typeof content.amount === "string" || + typeof content.amount === "number") + ); +} + +const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. + +Example response: +\`\`\`json +{ + "tokenAddress": "BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump", + "recipient": "9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", + "amount": "1000" +} +\`\`\` + +{{recentMessages}} + +Given the recent messages, extract the following information about the requested token transfer: +- Token contract address +- Recipient wallet address +- Amount to transfer + +Respond with a JSON markdown block containing only the extracted values.`; + +export default { + name: "SEND_TOKEN", + similes: [ + "TRANSFER_TOKEN", + "TRANSFER_TOKENS", + "SEND_TOKENS", + "SEND_SOL", + "PAY", + ], + validate: async (runtime: IAgentRuntime, message: Memory) => { + console.log("Validating transfer from user:", message.userId); + //add custom validate logic here + /* + const adminIds = runtime.getSetting("ADMIN_USER_IDS")?.split(",") || []; + //console.log("Admin IDs from settings:", adminIds); + + const isAdmin = adminIds.includes(message.userId); + + if (isAdmin) { + //console.log(`Authorized transfer from user: ${message.userId}`); + return true; + } + else + { + //console.log(`Unauthorized transfer attempt from user: ${message.userId}`); + return false; + } + */ + return false; + }, + description: "Transfer tokens from the agent's wallet to another address", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: { [key: string]: unknown }, + callback?: HandlerCallback + ): Promise => { + elizaLogger.log("Starting SEND_TOKEN handler..."); + + // Initialize or update state + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + // Compose transfer context + const transferContext = composeContext({ + state, + template: transferTemplate, + }); + + // Generate transfer content + const content = await generateObjectDeprecated({ + runtime, + context: transferContext, + modelClass: ModelClass.LARGE, + }); + + // Validate transfer content + if (!isTransferContent(runtime, content)) { + console.error("Invalid content for TRANSFER_TOKEN action."); + if (callback) { + callback({ + text: "Unable to process transfer request. Invalid content provided.", + content: { error: "Invalid transfer content" }, + }); + } + return false; + } + + try { + const { keypair: senderKeypair } = await getWalletKey( + runtime, + true + ); + + const connection = new Connection(settings.RPC_URL!); + + const mintPubkey = new PublicKey(content.tokenAddress); + const recipientPubkey = new PublicKey(content.recipient); + + // Get decimals (simplest way) + const mintInfo = await connection.getParsedAccountInfo(mintPubkey); + const decimals = + (mintInfo.value?.data as any)?.parsed?.info?.decimals ?? 9; + + // Adjust amount with decimals + const adjustedAmount = BigInt( + Number(content.amount) * Math.pow(10, decimals) + ); + console.log( + `Transferring: ${content.amount} tokens (${adjustedAmount} base units)` + ); + + // Rest of the existing working code... + const senderATA = getAssociatedTokenAddressSync( + mintPubkey, + senderKeypair.publicKey + ); + const recipientATA = getAssociatedTokenAddressSync( + mintPubkey, + recipientPubkey + ); + + const instructions = []; + + const recipientATAInfo = + await connection.getAccountInfo(recipientATA); + if (!recipientATAInfo) { + const { createAssociatedTokenAccountInstruction } = + await import("@solana/spl-token"); + instructions.push( + createAssociatedTokenAccountInstruction( + senderKeypair.publicKey, + recipientATA, + recipientPubkey, + mintPubkey + ) + ); + } + + instructions.push( + createTransferInstruction( + senderATA, + recipientATA, + senderKeypair.publicKey, + adjustedAmount + ) + ); + + // Create and sign versioned transaction + const messageV0 = new TransactionMessage({ + payerKey: senderKeypair.publicKey, + recentBlockhash: (await connection.getLatestBlockhash()) + .blockhash, + instructions, + }).compileToV0Message(); + + const transaction = new VersionedTransaction(messageV0); + transaction.sign([senderKeypair]); + + // Send transaction + const signature = await connection.sendTransaction(transaction); + + console.log("Transfer successful:", signature); + + if (callback) { + callback({ + text: `Successfully transferred ${content.amount} tokens to ${content.recipient}\nTransaction: ${signature}`, + content: { + success: true, + signature, + amount: content.amount, + recipient: content.recipient, + }, + }); + } + + return true; + } catch (error) { + console.error("Error during token transfer:", error); + if (callback) { + callback({ + text: `Error transferring tokens: ${error.message}`, + content: { error: error.message }, + }); + } + return false; + } + }, + + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Send 69 EZSIS BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", + }, + }, + { + user: "{{user2}}", + content: { + text: "I'll send 69 EZSIS tokens now...", + action: "SEND_TOKEN", + }, + }, + { + user: "{{user2}}", + content: { + text: "Successfully sent 69 EZSIS tokens to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa\nTransaction: 5KtPn3DXXzHkb7VAVHZGwXJQqww39ASnrf7YkyJoF2qAGEpBEEGvRHLnnTG8ZVwKqNHMqSckWVGnsQAgfH5pbxEb", + }, + }, + ], + ] as ActionExample[][], +} as Action; diff --git a/packages/plugin-solana-agentkit/src/bignumber.ts b/packages/plugin-solana-agentkit/src/bignumber.ts new file mode 100644 index 0000000000..f320676a0f --- /dev/null +++ b/packages/plugin-solana-agentkit/src/bignumber.ts @@ -0,0 +1,9 @@ +import BigNumber from "bignumber.js"; + +// Re-export BigNumber constructor +export const BN = BigNumber; + +// Helper function to create new BigNumber instances +export function toBN(value: string | number | BigNumber): BigNumber { + return new BigNumber(value); +} diff --git a/packages/plugin-solana-agentkit/src/environment.ts b/packages/plugin-solana-agentkit/src/environment.ts new file mode 100644 index 0000000000..e6931091c8 --- /dev/null +++ b/packages/plugin-solana-agentkit/src/environment.ts @@ -0,0 +1,76 @@ +import { IAgentRuntime } from "@elizaos/core"; +import { z } from "zod"; + +export const solanaEnvSchema = z + .object({ + WALLET_SECRET_SALT: z.string().optional(), + }) + .and( + z.union([ + z.object({ + WALLET_SECRET_KEY: z + .string() + .min(1, "Wallet secret key is required"), + WALLET_PUBLIC_KEY: z + .string() + .min(1, "Wallet public key is required"), + }), + z.object({ + WALLET_SECRET_SALT: z + .string() + .min(1, "Wallet secret salt is required"), + }), + ]) + ) + .and( + z.object({ + SOL_ADDRESS: z.string().min(1, "SOL address is required"), + SLIPPAGE: z.string().min(1, "Slippage is required"), + RPC_URL: z.string().min(1, "RPC URL is required"), + HELIUS_API_KEY: z.string().min(1, "Helius API key is required"), + BIRDEYE_API_KEY: z.string().min(1, "Birdeye API key is required"), + }) + ); + +export type SolanaConfig = z.infer; + +export async function validateSolanaConfig( + runtime: IAgentRuntime +): Promise { + try { + const config = { + WALLET_SECRET_SALT: + runtime.getSetting("WALLET_SECRET_SALT") || + process.env.WALLET_SECRET_SALT, + WALLET_SECRET_KEY: + runtime.getSetting("WALLET_SECRET_KEY") || + process.env.WALLET_SECRET_KEY, + WALLET_PUBLIC_KEY: + runtime.getSetting("SOLANA_PUBLIC_KEY") || + runtime.getSetting("WALLET_PUBLIC_KEY") || + process.env.WALLET_PUBLIC_KEY, + SOL_ADDRESS: + runtime.getSetting("SOL_ADDRESS") || process.env.SOL_ADDRESS, + SLIPPAGE: runtime.getSetting("SLIPPAGE") || process.env.SLIPPAGE, + RPC_URL: runtime.getSetting("RPC_URL") || process.env.RPC_URL, + HELIUS_API_KEY: + runtime.getSetting("HELIUS_API_KEY") || + process.env.HELIUS_API_KEY, + BIRDEYE_API_KEY: + runtime.getSetting("BIRDEYE_API_KEY") || + process.env.BIRDEYE_API_KEY, + }; + + return solanaEnvSchema.parse(config); + } catch (error) { + if (error instanceof z.ZodError) { + const errorMessages = error.errors + .map((err) => `${err.path.join(".")}: ${err.message}`) + .join("\n"); + throw new Error( + `Solana configuration validation failed:\n${errorMessages}` + ); + } + throw error; + } +} diff --git a/packages/plugin-solana-agentkit/src/evaluators/trust.ts b/packages/plugin-solana-agentkit/src/evaluators/trust.ts new file mode 100644 index 0000000000..2c4f441cf5 --- /dev/null +++ b/packages/plugin-solana-agentkit/src/evaluators/trust.ts @@ -0,0 +1,543 @@ +import { + composeContext, + generateObjectArray, + generateTrueOrFalse, + MemoryManager, + booleanFooter, + ActionExample, + Content, + IAgentRuntime, + Memory, + 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"; + +const shouldProcessTemplate = + `# Task: Decide if the recent messages should be processed for token recommendations. + + Look for messages that: + - Mention specific token tickers or contract addresses + - Contain words related to buying, selling, or trading tokens + - Express opinions or convictions about tokens + + Based on the following conversation, should the messages be processed for recommendations? YES or NO + + {{recentMessages}} + + Should the messages be processed for recommendations? ` + booleanFooter; + +export const formatRecommendations = (recommendations: Memory[]) => { + const messageStrings = recommendations + .reverse() + .map((rec: Memory) => `${(rec.content as Content)?.content}`); + const finalMessageStrings = messageStrings.join("\n"); + return finalMessageStrings; +}; + +const recommendationTemplate = `TASK: Extract recommendations to buy or sell memecoins from the conversation as an array of objects in JSON format. + + Memecoins usually have a ticker and a contract address. Additionally, recommenders may make recommendations with some amount of conviction. The amount of conviction in their recommendation can be none, low, medium, or high. Recommenders can make recommendations to buy, not buy, sell and not sell. + +# START OF EXAMPLES +These are an examples of the expected output of this task: +{{evaluationExamples}} +# END OF EXAMPLES + +# INSTRUCTIONS + +Extract any new recommendations from the conversation that are not already present in the list of known recommendations below: +{{recentRecommendations}} + +- Include the recommender's username +- Try not to include already-known recommendations. If you think a recommendation is already known, but you're not sure, respond with alreadyKnown: true. +- Set the conviction to 'none', 'low', 'medium' or 'high' +- Set the recommendation type to 'buy', 'dont_buy', 'sell', or 'dont_sell' +- Include the contract address and/or ticker if available + +Recent Messages: +{{recentMessages}} + +Response should be a JSON object array inside a JSON markdown block. Correct response format: +\`\`\`json +[ + { + "recommender": string, + "ticker": string | null, + "contractAddress": string | null, + "type": enum, + "conviction": enum, + "alreadyKnown": boolean + }, + ... +] +\`\`\``; + +async function handler(runtime: IAgentRuntime, message: Memory) { + console.log("Evaluating for trust"); + const state = await runtime.composeState(message); + + const { agentId, roomId } = state; + + // Check if we should process the messages + const shouldProcessContext = composeContext({ + state, + template: shouldProcessTemplate, + }); + + const shouldProcess = await generateTrueOrFalse({ + context: shouldProcessContext, + modelClass: ModelClass.SMALL, + runtime, + }); + + if (!shouldProcess) { + console.log("Skipping process"); + return []; + } + + console.log("Processing recommendations"); + + // Get recent recommendations + const recommendationsManager = new MemoryManager({ + runtime, + tableName: "recommendations", + }); + + const recentRecommendations = await recommendationsManager.getMemories({ + roomId, + count: 20, + }); + + const context = composeContext({ + state: { + ...state, + recentRecommendations: formatRecommendations(recentRecommendations), + }, + template: recommendationTemplate, + }); + + const recommendations = await generateObjectArray({ + runtime, + context, + modelClass: ModelClass.LARGE, + }); + + console.log("recommendations", recommendations); + + if (!recommendations) { + return []; + } + + // If the recommendation is already known or corrupted, remove it + const filteredRecommendations = recommendations.filter((rec) => { + return ( + !rec.alreadyKnown && + (rec.ticker || rec.contractAddress) && + rec.recommender && + rec.conviction && + rec.recommender.trim() !== "" + ); + }); + + const { publicKey } = await getWalletKey(runtime, false); + + for (const rec of filteredRecommendations) { + // create the wallet provider and token provider + const walletProvider = new WalletProvider( + new Connection( + runtime.getSetting("RPC_URL") || + "https://api.mainnet-beta.solana.com" + ), + publicKey + ); + const tokenProvider = new TokenProvider( + rec.contractAddress, + walletProvider, + runtime.cacheManager + ); + + // TODO: Check to make sure the contract address is valid, it's the right one, etc + + // + if (!rec.contractAddress) { + const tokenAddress = await tokenProvider.getTokenFromWallet( + runtime, + rec.ticker + ); + rec.contractAddress = tokenAddress; + if (!tokenAddress) { + // try to search for the symbol and return the contract address with they highest liquidity and market cap + const result = await tokenProvider.searchDexScreenerData( + rec.ticker + ); + const tokenAddress = result?.baseToken?.address; + rec.contractAddress = tokenAddress; + if (!tokenAddress) { + console.warn("Could not find contract address for token"); + continue; + } + } + } + + // create the trust score manager + + const trustScoreDb = new TrustScoreDatabase(runtime.databaseAdapter.db); + const trustScoreManager = new TrustScoreManager( + runtime, + tokenProvider, + trustScoreDb + ); + + // get actors from the database + const participants = + await runtime.databaseAdapter.getParticipantsForRoom( + message.roomId + ); + + // find the first user Id from a user with the username that we extracted + const user = participants.find(async (actor) => { + const user = await runtime.databaseAdapter.getAccountById(actor); + return ( + user.name.toLowerCase().trim() === + rec.recommender.toLowerCase().trim() + ); + }); + + if (!user) { + console.warn("Could not find user: ", rec.recommender); + continue; + } + + const account = await runtime.databaseAdapter.getAccountById(user); + const userId = account.id; + + const recMemory = { + userId, + agentId, + content: { text: JSON.stringify(rec) }, + roomId, + createdAt: Date.now(), + }; + + await recommendationsManager.createMemory(recMemory, true); + + console.log("recommendationsManager", rec); + + // - from here we just need to make sure code is right + + // buy, dont buy, sell, dont sell + + const buyAmounts = await tokenProvider.calculateBuyAmounts(); + + let buyAmount = buyAmounts[rec.conviction.toLowerCase().trim()]; + if (!buyAmount) { + // handle annoying cases + // for now just put in 10 sol + buyAmount = 10; + } + + // TODO: is this is a buy, sell, dont buy, or dont sell? + const shouldTrade = await tokenProvider.shouldTradeToken(); + + if (!shouldTrade) { + console.warn( + "There might be a problem with the token, not trading" + ); + continue; + } + + switch (rec.type) { + case "buy": + // for now, lets just assume buy only, but we should implement + await trustScoreManager.createTradePerformance( + runtime, + rec.contractAddress, + userId, + { + buy_amount: rec.buyAmount, + is_simulation: true, + } + ); + break; + case "sell": + case "dont_sell": + case "dont_buy": + console.warn("Not implemented"); + break; + } + } + + return filteredRecommendations; +} + +export const trustEvaluator: Evaluator = { + name: "EXTRACT_RECOMMENDATIONS", + similes: [ + "GET_RECOMMENDATIONS", + "EXTRACT_TOKEN_RECS", + "EXTRACT_MEMECOIN_RECS", + ], + alwaysRun: true, + validate: async ( + runtime: IAgentRuntime, + message: Memory + ): Promise => { + if (message.content.text.length < 5) { + return false; + } + + return message.userId !== message.agentId; + }, + description: + "Extract recommendations to buy or sell memecoins/tokens from the conversation, including details like ticker, contract address, conviction level, and recommender username.", + handler, + examples: [ + { + context: `Actors in the scene: +{{user1}}: Experienced DeFi degen. Constantly chasing high yield farms. +{{user2}}: New to DeFi, learning the ropes. + +Recommendations about the actors: +None`, + messages: [ + { + user: "{{user1}}", + content: { + text: "Yo, have you checked out $SOLARUG? Dope new yield aggregator on Solana.", + }, + }, + { + user: "{{user2}}", + content: { + text: "Nah, I'm still trying to wrap my head around how yield farming even works haha. Is it risky?", + }, + }, + { + user: "{{user1}}", + content: { + text: "I mean, there's always risk in DeFi, but the $SOLARUG devs seem legit. Threw a few sol into the FCweoTfJ128jGgNEXgdfTXdEZVk58Bz9trCemr6sXNx9 vault, farming's been smooth so far.", + }, + }, + ] as ActionExample[], + outcome: `\`\`\`json +[ + { + "recommender": "{{user1}}", + "ticker": "SOLARUG", + "contractAddress": "FCweoTfJ128jGgNEXgdfTXdEZVk58Bz9trCemr6sXNx9", + "type": "buy", + "conviction": "medium", + "alreadyKnown": false + } +] +\`\`\``, + }, + + { + context: `Actors in the scene: +{{user1}}: Solana maximalist. Believes Solana will flip Ethereum. +{{user2}}: Multichain proponent. Holds both SOL and ETH. + +Recommendations about the actors: +{{user1}} has previously promoted $COPETOKEN and $SOYLENT.`, + messages: [ + { + user: "{{user1}}", + content: { + text: "If you're not long $SOLVAULT at 7tRzKud6FBVFEhYqZS3CuQ2orLRM21bdisGykL5Sr4Dx, you're missing out. This will be the blackhole of Solana liquidity.", + }, + }, + { + user: "{{user2}}", + content: { + text: "Idk man, feels like there's a new 'vault' or 'reserve' token every week on Sol. What happened to $COPETOKEN and $SOYLENT that you were shilling before?", + }, + }, + { + user: "{{user1}}", + content: { + text: "$COPETOKEN and $SOYLENT had their time, I took profits near the top. But $SOLVAULT is different, it has actual utility. Do what you want, but don't say I didn't warn you when this 50x's and you're left holding your $ETH bags.", + }, + }, + ] as ActionExample[], + outcome: `\`\`\`json +[ + { + "recommender": "{{user1}}", + "ticker": "COPETOKEN", + "contractAddress": null, + "type": "sell", + "conviction": "low", + "alreadyKnown": true + }, + { + "recommender": "{{user1}}", + "ticker": "SOYLENT", + "contractAddress": null, + "type": "sell", + "conviction": "low", + "alreadyKnown": true + }, + { + "recommender": "{{user1}}", + "ticker": "SOLVAULT", + "contractAddress": "7tRzKud6FBVFEhYqZS3CuQ2orLRM21bdisGykL5Sr4Dx", + "type": "buy", + "conviction": "high", + "alreadyKnown": false + } +] +\`\`\``, + }, + + { + context: `Actors in the scene: +{{user1}}: Self-proclaimed Solana alpha caller. Allegedly has insider info. +{{user2}}: Degen gambler. Will ape into any hyped token. + +Recommendations about the actors: +None`, + messages: [ + { + user: "{{user1}}", + content: { + text: "I normally don't do this, but I like you anon, so I'll let you in on some alpha. $ROULETTE at 48vV5y4DRH1Adr1bpvSgFWYCjLLPtHYBqUSwNc2cmCK2 is going to absolutely send it soon. You didn't hear it from me 🤐", + }, + }, + { + user: "{{user2}}", + content: { + text: "Oh shit, insider info from the alpha god himself? Say no more, I'm aping in hard.", + }, + }, + ] as ActionExample[], + outcome: `\`\`\`json +[ + { + "recommender": "{{user1}}", + "ticker": "ROULETTE", + "contractAddress": "48vV5y4DRH1Adr1bpvSgFWYCjLLPtHYBqUSwNc2cmCK2", + "type": "buy", + "conviction": "high", + "alreadyKnown": false + } +] +\`\`\``, + }, + + { + context: `Actors in the scene: +{{user1}}: NFT collector and trader. Bullish on Solana NFTs. +{{user2}}: Only invests based on fundamentals. Sees all NFTs as worthless JPEGs. + +Recommendations about the actors: +None +`, + messages: [ + { + user: "{{user1}}", + content: { + text: "GM. I'm heavily accumulating $PIXELAPE, the token for the Pixel Ape Yacht Club NFT collection. 10x is inevitable.", + }, + }, + { + user: "{{user2}}", + content: { + text: "NFTs are a scam bro. There's no underlying value. You're essentially trading worthless JPEGs.", + }, + }, + { + user: "{{user1}}", + content: { + text: "Fun staying poor 🤡 $PIXELAPE is about to moon and you'll be left behind.", + }, + }, + { + user: "{{user2}}", + content: { + text: "Whatever man, I'm not touching that shit with a ten foot pole. Have fun holding your bags.", + }, + }, + { + user: "{{user1}}", + content: { + text: "Don't need luck where I'm going 😎 Once $PIXELAPE at 3hAKKmR6XyBooQBPezCbUMhrmcyTkt38sRJm2thKytWc takes off, you'll change your tune.", + }, + }, + ], + outcome: `\`\`\`json +[ + { + "recommender": "{{user1}}", + "ticker": "PIXELAPE", + "contractAddress": "3hAKKmR6XyBooQBPezCbUMhrmcyTkt38sRJm2thKytWc", + "type": "buy", + "conviction": "high", + "alreadyKnown": false + } +] +\`\`\``, + }, + + { + context: `Actors in the scene: +{{user1}}: Contrarian investor. Bets against hyped projects. +{{user2}}: Trend follower. Buys tokens that are currently popular. + +Recommendations about the actors: +None`, + messages: [ + { + user: "{{user2}}", + content: { + text: "$SAMOYED is the talk of CT right now. Making serious moves. Might have to get a bag.", + }, + }, + { + user: "{{user1}}", + content: { + text: "Whenever a token is the 'talk of CT', that's my cue to short it. $SAMOYED is going to dump hard, mark my words.", + }, + }, + { + user: "{{user2}}", + content: { + text: "Idk man, the hype seems real this time. 5TQwHyZbedaH4Pcthj1Hxf5GqcigL6qWuB7YEsBtqvhr chart looks bullish af.", + }, + }, + { + user: "{{user1}}", + content: { + text: "Hype is always real until it isn't. I'm taking out a fat short position here. Don't say I didn't warn you when this crashes 90% and you're left holding the flaming bags.", + }, + }, + ], + outcome: `\`\`\`json +[ + { + "recommender": "{{user2}}", + "ticker": "SAMOYED", + "contractAddress": "5TQwHyZbedaH4Pcthj1Hxf5GqcigL6qWuB7YEsBtqvhr", + "type": "buy", + "conviction": "medium", + "alreadyKnown": false + }, + { + "recommender": "{{user1}}", + "ticker": "SAMOYED", + "contractAddress": "5TQwHyZbedaH4Pcthj1Hxf5GqcigL6qWuB7YEsBtqvhr", + "type": "dont_buy", + "conviction": "high", + "alreadyKnown": false + } +] +\`\`\``, + }, + ], +}; diff --git a/packages/plugin-solana-agentkit/src/index.ts b/packages/plugin-solana-agentkit/src/index.ts new file mode 100644 index 0000000000..210b12787b --- /dev/null +++ b/packages/plugin-solana-agentkit/src/index.ts @@ -0,0 +1,24 @@ +export * from "./providers/token.ts"; +export * from "./providers/wallet.ts"; +export * from "./providers/trustScoreProvider.ts"; +export * from "./evaluators/trust.ts"; + +import { Plugin } from "@elizaos/core"; +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"; + +export { TokenProvider, WalletProvider }; + +export const solanaAgentkitPlguin: Plugin = { + name: "solana", + description: "Solana Plugin with solana agent kit for Eliza", + actions: [transferToken], + evaluators: [trustEvaluator], + providers: [walletProvider, trustScoreProvider], +}; + +export default solanaAgentkitPlguin; diff --git a/packages/plugin-solana-agentkit/src/keypairUtils.ts b/packages/plugin-solana-agentkit/src/keypairUtils.ts new file mode 100644 index 0000000000..4aa942ebed --- /dev/null +++ b/packages/plugin-solana-agentkit/src/keypairUtils.ts @@ -0,0 +1,82 @@ +import { Keypair, PublicKey } from "@solana/web3.js"; +import { DeriveKeyProvider, TEEMode } from "@elizaos/plugin-tee"; +import bs58 from "bs58"; +import { IAgentRuntime } from "@elizaos/core"; + +export interface KeypairResult { + keypair?: Keypair; + publicKey?: PublicKey; +} + +/** + * Gets either a keypair or public key based on TEE mode and runtime settings + * @param runtime The agent runtime + * @param requirePrivateKey Whether to return a full keypair (true) or just public key (false) + * @returns KeypairResult containing either keypair or public key + */ +export async function getWalletKey( + runtime: IAgentRuntime, + requirePrivateKey: boolean = true +): Promise { + const teeMode = runtime.getSetting("TEE_MODE") || TEEMode.OFF; + + if (teeMode !== TEEMode.OFF) { + const walletSecretSalt = runtime.getSetting("WALLET_SECRET_SALT"); + if (!walletSecretSalt) { + throw new Error( + "WALLET_SECRET_SALT required when TEE_MODE is enabled" + ); + } + + const deriveKeyProvider = new DeriveKeyProvider(teeMode); + const deriveKeyResult = await deriveKeyProvider.deriveEd25519Keypair( + "/", + walletSecretSalt, + runtime.agentId + ); + + return requirePrivateKey + ? { keypair: deriveKeyResult.keypair } + : { publicKey: deriveKeyResult.keypair.publicKey }; + } + + // TEE mode is OFF + if (requirePrivateKey) { + const privateKeyString = + runtime.getSetting("SOLANA_PRIVATE_KEY") ?? + runtime.getSetting("WALLET_PRIVATE_KEY"); + + if (!privateKeyString) { + throw new Error("Private key not found in settings"); + } + + try { + // First try base58 + const secretKey = bs58.decode(privateKeyString); + return { keypair: Keypair.fromSecretKey(secretKey) }; + } catch (e) { + console.log("Error decoding base58 private key:", e); + try { + // Then try base64 + console.log("Try decoding base64 instead"); + const secretKey = Uint8Array.from( + Buffer.from(privateKeyString, "base64") + ); + return { keypair: Keypair.fromSecretKey(secretKey) }; + } catch (e2) { + console.error("Error decoding private key: ", e2); + throw new Error("Invalid private key format"); + } + } + } else { + const publicKeyString = + runtime.getSetting("SOLANA_PUBLIC_KEY") ?? + runtime.getSetting("WALLET_PUBLIC_KEY"); + + if (!publicKeyString) { + throw new Error("Public key not found in settings"); + } + + return { publicKey: new PublicKey(publicKeyString) }; + } +} diff --git a/packages/plugin-solana-agentkit/src/providers/orderBook.ts b/packages/plugin-solana-agentkit/src/providers/orderBook.ts new file mode 100644 index 0000000000..ac4577e012 --- /dev/null +++ b/packages/plugin-solana-agentkit/src/providers/orderBook.ts @@ -0,0 +1,45 @@ +import { IAgentRuntime, Memory, Provider, State } from "@elizaos/core"; +interface Order { + userId: string; + ticker: string; + contractAddress: string; + timestamp: string; + buyAmount: number; + price: number; +} + +const orderBookProvider: Provider = { + get: async (runtime: IAgentRuntime, message: Memory, _state?: State) => { + const userId = message.userId; + + // Read the order book from the JSON file + const orderBookPath = + runtime.getSetting("orderBookPath") ?? "solana/orderBook"; + + const orderBook: Order[] = []; + + const cachedOrderBook = + await runtime.cacheManager.get(orderBookPath); + + if (cachedOrderBook) { + orderBook.push(...cachedOrderBook); + } + + // Filter the orders for the current user + const userOrders = orderBook.filter((order) => order.userId === userId); + + let totalProfit = 0; + for (const order of userOrders) { + // Get the current price of the asset (replace with actual price fetching logic) + const currentPrice = 120; + + const priceDifference = currentPrice - order.price; + const orderProfit = priceDifference * order.buyAmount; + totalProfit += orderProfit; + } + + return `The user has made a total profit of $${totalProfit.toFixed(2)} for the agent based on their recorded buy orders.`; + }, +}; + +export { orderBookProvider }; diff --git a/packages/plugin-solana-agentkit/src/providers/simulationSellingService.ts b/packages/plugin-solana-agentkit/src/providers/simulationSellingService.ts new file mode 100644 index 0000000000..670eeb74f3 --- /dev/null +++ b/packages/plugin-solana-agentkit/src/providers/simulationSellingService.ts @@ -0,0 +1,501 @@ +import { + TrustScoreDatabase, + TokenPerformance, + // TradePerformance, + TokenRecommendation, +} from "@elizaos/plugin-trustdb"; +import { Connection, PublicKey } from "@solana/web3.js"; +// Assuming TokenProvider and IAgentRuntime are available +import { TokenProvider } from "./token.ts"; +// import { settings } from "@elizaos/core"; +import { IAgentRuntime } from "@elizaos/core"; +import { WalletProvider } from "./wallet.ts"; +import * as amqp from "amqplib"; +import { ProcessedTokenData } from "../types/token.ts"; +import { getWalletKey } from "../keypairUtils.ts"; + +interface SellDetails { + sell_amount: number; + sell_recommender_id: string | null; +} + +export class SimulationSellingService { + private trustScoreDb: TrustScoreDatabase; + private walletProvider: WalletProvider; + private connection: Connection; + private baseMint: PublicKey; + private DECAY_RATE = 0.95; + private MAX_DECAY_DAYS = 30; + private backend: string; + private backendToken: string; + private amqpConnection: amqp.Connection; + private amqpChannel: amqp.Channel; + private sonarBe: string; + private sonarBeToken: string; + private runtime: IAgentRuntime; + + private runningProcesses: Set = new Set(); + + constructor(runtime: IAgentRuntime, trustScoreDb: TrustScoreDatabase) { + this.trustScoreDb = trustScoreDb; + + this.connection = new Connection(runtime.getSetting("RPC_URL")); + this.initializeWalletProvider(); + this.baseMint = new PublicKey( + runtime.getSetting("BASE_MINT") || + "So11111111111111111111111111111111111111112" + ); + this.backend = runtime.getSetting("BACKEND_URL"); + this.backendToken = runtime.getSetting("BACKEND_TOKEN"); + this.initializeRabbitMQ(runtime.getSetting("AMQP_URL")); + this.sonarBe = runtime.getSetting("SONAR_BE"); + this.sonarBeToken = runtime.getSetting("SONAR_BE_TOKEN"); + this.runtime = runtime; + } + /** + * Initializes the RabbitMQ connection and starts consuming messages. + * @param amqpUrl The RabbitMQ server URL. + */ + private async initializeRabbitMQ(amqpUrl: string) { + try { + this.amqpConnection = await amqp.connect(amqpUrl); + this.amqpChannel = await this.amqpConnection.createChannel(); + console.log("Connected to RabbitMQ"); + // Start consuming messages + this.consumeMessages(); + } catch (error) { + console.error("Failed to connect to RabbitMQ:", error); + } + } + + /** + * Sets up the consumer for the specified RabbitMQ queue. + */ + private async consumeMessages() { + const queue = "process_eliza_simulation"; + await this.amqpChannel.assertQueue(queue, { durable: true }); + this.amqpChannel.consume( + queue, + (msg) => { + if (msg !== null) { + const content = msg.content.toString(); + this.processMessage(content); + this.amqpChannel.ack(msg); + } + }, + { noAck: false } + ); + console.log(`Listening for messages on queue: ${queue}`); + } + + /** + * Processes incoming messages from RabbitMQ. + * @param message The message content as a string. + */ + private async processMessage(message: string) { + try { + const { tokenAddress, amount, sell_recommender_id } = + JSON.parse(message); + console.log( + `Received message for token ${tokenAddress} to sell ${amount}` + ); + + const decision: SellDecision = { + tokenPerformance: + await this.trustScoreDb.getTokenPerformance(tokenAddress), + amountToSell: amount, + sell_recommender_id: sell_recommender_id, + }; + + // Execute the sell + await this.executeSellDecision(decision); + + // Remove from running processes after completion + this.runningProcesses.delete(tokenAddress); + } catch (error) { + console.error("Error processing message:", error); + } + } + + /** + * Executes a single sell decision. + * @param decision The sell decision containing token performance and amount to sell. + */ + private async executeSellDecision(decision: SellDecision) { + const { tokenPerformance, amountToSell, sell_recommender_id } = + decision; + const tokenAddress = tokenPerformance.tokenAddress; + + try { + console.log( + `Executing sell for token ${tokenPerformance.symbol}: ${amountToSell}` + ); + + // Update the sell details + const sellDetails: SellDetails = { + sell_amount: amountToSell, + sell_recommender_id: sell_recommender_id, // Adjust if necessary + }; + const sellTimeStamp = new Date().toISOString(); + const tokenProvider = new TokenProvider( + tokenAddress, + this.walletProvider, + this.runtime.cacheManager + ); + + // Update sell details in the database + const sellDetailsData = await this.updateSellDetails( + tokenAddress, + sell_recommender_id, + sellTimeStamp, + sellDetails, + true, // isSimulation + tokenProvider + ); + + console.log("Sell order executed successfully", sellDetailsData); + + // check if balance is zero and remove token from running processes + const balance = this.trustScoreDb.getTokenBalance(tokenAddress); + if (balance === 0) { + this.runningProcesses.delete(tokenAddress); + } + // stop the process in the sonar backend + await this.stopProcessInTheSonarBackend(tokenAddress); + } catch (error) { + console.error( + `Error executing sell for token ${tokenAddress}:`, + error + ); + } + } + + /** + * Derives the public key based on the TEE (Trusted Execution Environment) mode and initializes the wallet provider. + * If TEE mode is enabled, derives a keypair using the DeriveKeyProvider with the wallet secret salt and agent ID. + * If TEE mode is disabled, uses the provided Solana public key or wallet public key from settings. + */ + private async initializeWalletProvider(): Promise { + const { publicKey } = await getWalletKey(this.runtime, false); + + this.walletProvider = new WalletProvider(this.connection, publicKey); + } + + public async startService() { + // starting the service + console.log("Starting SellingService..."); + await this.startListeners(); + } + + public async startListeners() { + // scanning recommendations and selling + console.log("Scanning for token performances..."); + const tokenPerformances = + await this.trustScoreDb.getAllTokenPerformancesWithBalance(); + + await this.processTokenPerformances(tokenPerformances); + } + + private processTokenPerformances(tokenPerformances: TokenPerformance[]) { + // To Do: logic when to sell and how much + console.log("Deciding when to sell and how much..."); + const runningProcesses = this.runningProcesses; + // remove running processes from tokenPerformances + tokenPerformances = tokenPerformances.filter( + (tp) => !runningProcesses.has(tp.tokenAddress) + ); + + // start the process in the sonar backend + tokenPerformances.forEach(async (tokenPerformance) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const tokenProvider = new TokenProvider( + tokenPerformance.tokenAddress, + this.walletProvider, + this.runtime.cacheManager + ); + // const shouldTrade = await tokenProvider.shouldTradeToken(); + // if (shouldTrade) { + const tokenRecommendations: TokenRecommendation[] = + this.trustScoreDb.getRecommendationsByToken( + tokenPerformance.tokenAddress + ); + const tokenRecommendation: TokenRecommendation = + tokenRecommendations[0]; + const balance = tokenPerformance.balance; + const sell_recommender_id = tokenRecommendation.recommenderId; + const tokenAddress = tokenPerformance.tokenAddress; + const process = await this.startProcessInTheSonarBackend( + tokenAddress, + balance, + true, + sell_recommender_id, + tokenPerformance.initialMarketCap + ); + if (process) { + this.runningProcesses.add(tokenAddress); + } + // } + }); + } + + public processTokenPerformance( + tokenAddress: string, + recommenderId: string + ) { + try { + const runningProcesses = this.runningProcesses; + // check if token is already being processed + if (runningProcesses.has(tokenAddress)) { + console.log(`Token ${tokenAddress} is already being processed`); + return; + } + const tokenPerformance = + this.trustScoreDb.getTokenPerformance(tokenAddress); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const tokenProvider = new TokenProvider( + tokenPerformance.tokenAddress, + this.walletProvider, + this.runtime.cacheManager + ); + const balance = tokenPerformance.balance; + const sell_recommender_id = recommenderId; + const process = this.startProcessInTheSonarBackend( + tokenAddress, + balance, + true, + sell_recommender_id, + tokenPerformance.initialMarketCap + ); + if (process) { + this.runningProcesses.add(tokenAddress); + } + } catch (error) { + console.error( + `Error getting token performance for token ${tokenAddress}:`, + error + ); + } + } + + private async startProcessInTheSonarBackend( + tokenAddress: string, + balance: number, + isSimulation: boolean, + sell_recommender_id: string, + initial_mc: number + ) { + try { + const message = JSON.stringify({ + tokenAddress, + balance, + isSimulation, + initial_mc, + sell_recommender_id, + }); + const response = await fetch( + `${this.sonarBe}/elizaos-sol/startProcess`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": `${this.sonarBeToken}`, + }, + body: message, + } + ); + + if (!response.ok) { + console.error( + `Failed to send message to process token ${tokenAddress}` + ); + return; + } + + const result = await response.json(); + console.log("Received response:", result); + console.log(`Sent message to process token ${tokenAddress}`); + + return result; + } catch (error) { + console.error( + `Error sending message to process token ${tokenAddress}:`, + error + ); + return null; + } + } + + private stopProcessInTheSonarBackend(tokenAddress: string) { + try { + return fetch(`${this.sonarBe}/elizaos-sol/stopProcess`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": `${this.sonarBeToken}`, + }, + body: JSON.stringify({ tokenAddress }), + }); + } catch (error) { + console.error( + `Error stopping process for token ${tokenAddress}:`, + error + ); + } + } + + async updateSellDetails( + tokenAddress: string, + recommenderId: string, + sellTimeStamp: string, + sellDetails: SellDetails, + isSimulation: boolean, + tokenProvider: TokenProvider + ) { + const recommender = + await this.trustScoreDb.getOrCreateRecommenderWithTelegramId( + recommenderId + ); + const processedData: ProcessedTokenData = + await tokenProvider.getProcessedTokenData(); + const prices = await this.walletProvider.fetchPrices(null); + const solPrice = prices.solana.usd; + const sellSol = sellDetails.sell_amount / parseFloat(solPrice); + const sell_value_usd = + sellDetails.sell_amount * processedData.tradeData.price; + const trade = await this.trustScoreDb.getLatestTradePerformance( + tokenAddress, + recommender.id, + isSimulation + ); + const buyTimeStamp = trade.buy_timeStamp; + const marketCap = + processedData.dexScreenerData.pairs[0]?.marketCap || 0; + const liquidity = + processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0; + const sell_price = processedData.tradeData.price; + const profit_usd = sell_value_usd - trade.buy_value_usd; + const profit_percent = (profit_usd / trade.buy_value_usd) * 100; + + const market_cap_change = marketCap - trade.buy_market_cap; + const liquidity_change = liquidity - trade.buy_liquidity; + + const isRapidDump = await this.isRapidDump(tokenAddress, tokenProvider); + + const sellDetailsData = { + sell_price: sell_price, + sell_timeStamp: sellTimeStamp, + sell_amount: sellDetails.sell_amount, + received_sol: sellSol, + sell_value_usd: sell_value_usd, + profit_usd: profit_usd, + profit_percent: profit_percent, + sell_market_cap: marketCap, + market_cap_change: market_cap_change, + sell_liquidity: liquidity, + liquidity_change: liquidity_change, + rapidDump: isRapidDump, + sell_recommender_id: sellDetails.sell_recommender_id || null, + }; + this.trustScoreDb.updateTradePerformanceOnSell( + tokenAddress, + recommender.id, + buyTimeStamp, + sellDetailsData, + isSimulation + ); + + // If the trade is a simulation update the balance + const oldBalance = this.trustScoreDb.getTokenBalance(tokenAddress); + const tokenBalance = oldBalance - sellDetails.sell_amount; + this.trustScoreDb.updateTokenBalance(tokenAddress, tokenBalance); + // generate some random hash for simulations + const hash = Math.random().toString(36).substring(7); + const transaction = { + tokenAddress: tokenAddress, + type: "sell" as "buy" | "sell", + transactionHash: hash, + amount: sellDetails.sell_amount, + price: processedData.tradeData.price, + isSimulation: true, + timestamp: new Date().toISOString(), + }; + this.trustScoreDb.addTransaction(transaction); + this.updateTradeInBe( + tokenAddress, + recommender.id, + recommender.telegramId, + sellDetailsData, + tokenBalance + ); + + return sellDetailsData; + } + async isRapidDump( + tokenAddress: string, + tokenProvider: TokenProvider + ): Promise { + const processedData: ProcessedTokenData = + await tokenProvider.getProcessedTokenData(); + console.log(`Fetched processed token data for token: ${tokenAddress}`); + + return processedData.tradeData.trade_24h_change_percent < -50; + } + + async delay(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + async updateTradeInBe( + tokenAddress: string, + recommenderId: string, + username: string, + data: SellDetails, + balanceLeft: number, + retries = 3, + delayMs = 2000 + ) { + for (let attempt = 1; attempt <= retries; attempt++) { + try { + await fetch( + `${this.backend}/api/updaters/updateTradePerformance`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.backendToken}`, + }, + body: JSON.stringify({ + tokenAddress: tokenAddress, + tradeData: data, + recommenderId: recommenderId, + username: username, + isSimulation: true, + balanceLeft: balanceLeft, + }), + } + ); + // If the request is successful, exit the loop + return; + } catch (error) { + console.error( + `Attempt ${attempt} failed: Error creating trade in backend`, + error + ); + if (attempt < retries) { + console.log(`Retrying in ${delayMs} ms...`); + await this.delay(delayMs); // Wait for the specified delay before retrying + } else { + console.error("All attempts failed."); + } + } + } + } +} + +// SellDecision interface +interface SellDecision { + tokenPerformance: TokenPerformance; + amountToSell: number; + sell_recommender_id: string | null; +} diff --git a/packages/plugin-solana-agentkit/src/providers/token.ts b/packages/plugin-solana-agentkit/src/providers/token.ts new file mode 100644 index 0000000000..d8e885915a --- /dev/null +++ b/packages/plugin-solana-agentkit/src/providers/token.ts @@ -0,0 +1,1124 @@ +import { ICacheManager, settings } from "@elizaos/core"; +import { IAgentRuntime, Memory, Provider, State } from "@elizaos/core"; +import { + DexScreenerData, + DexScreenerPair, + HolderData, + ProcessedTokenData, + TokenSecurityData, + TokenTradeData, + CalculatedBuyAmounts, + Prices, + TokenCodex, +} from "../types/token.ts"; +import NodeCache from "node-cache"; +import * as path from "path"; +import { toBN } from "../bignumber.ts"; +import { WalletProvider, Item } from "./wallet.ts"; +import { Connection } from "@solana/web3.js"; +import { getWalletKey } from "../keypairUtils.ts"; + +const PROVIDER_CONFIG = { + BIRDEYE_API: "https://public-api.birdeye.so", + MAX_RETRIES: 3, + RETRY_DELAY: 2000, + DEFAULT_RPC: "https://api.mainnet-beta.solana.com", + TOKEN_ADDRESSES: { + SOL: "So11111111111111111111111111111111111111112", + BTC: "qfnqNqs3nCAHjnyCgLRDbBtq4p2MtHZxw8YjSyYhPoL", + ETH: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + Example: "2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh", + }, + TOKEN_SECURITY_ENDPOINT: "/defi/token_security?address=", + TOKEN_TRADE_DATA_ENDPOINT: "/defi/v3/token/trade-data/single?address=", + DEX_SCREENER_API: "https://api.dexscreener.com/latest/dex/tokens/", + MAIN_WALLET: "", +}; + +export class TokenProvider { + private cache: NodeCache; + private cacheKey: string = "solana/tokens"; + private NETWORK_ID = 1399811149; + private GRAPHQL_ENDPOINT = "https://graph.codex.io/graphql"; + + constructor( + // private connection: Connection, + private tokenAddress: string, + private walletProvider: WalletProvider, + private cacheManager: ICacheManager + ) { + this.cache = new NodeCache({ stdTTL: 300 }); // 5 minutes cache + } + + private async readFromCache(key: string): Promise { + const cached = await this.cacheManager.get( + path.join(this.cacheKey, key) + ); + return cached; + } + + private async writeToCache(key: string, data: T): Promise { + await this.cacheManager.set(path.join(this.cacheKey, key), data, { + expires: Date.now() + 5 * 60 * 1000, + }); + } + + private async getCachedData(key: string): Promise { + // Check in-memory cache first + const cachedData = this.cache.get(key); + if (cachedData) { + return cachedData; + } + + // Check file-based cache + const fileCachedData = await this.readFromCache(key); + if (fileCachedData) { + // Populate in-memory cache + this.cache.set(key, fileCachedData); + return fileCachedData; + } + + return null; + } + + private async setCachedData(cacheKey: string, data: T): Promise { + // Set in-memory cache + this.cache.set(cacheKey, data); + + // Write to file-based cache + await this.writeToCache(cacheKey, data); + } + + private async fetchWithRetry( + url: string, + options: RequestInit = {} + ): Promise { + let lastError: Error; + + for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) { + try { + const response = await fetch(url, { + ...options, + headers: { + Accept: "application/json", + "x-chain": "solana", + "X-API-KEY": settings.BIRDEYE_API_KEY || "", + ...options.headers, + }, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error( + `HTTP error! status: ${response.status}, message: ${errorText}` + ); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error(`Attempt ${i + 1} failed:`, error); + lastError = error as Error; + if (i < PROVIDER_CONFIG.MAX_RETRIES - 1) { + const delay = PROVIDER_CONFIG.RETRY_DELAY * Math.pow(2, i); + console.log(`Waiting ${delay}ms before retrying...`); + await new Promise((resolve) => setTimeout(resolve, delay)); + continue; + } + } + } + + console.error( + "All attempts failed. Throwing the last error:", + lastError + ); + throw lastError; + } + + async getTokensInWallet(runtime: IAgentRuntime): Promise { + const walletInfo = + await this.walletProvider.fetchPortfolioValue(runtime); + const items = walletInfo.items; + return items; + } + + // check if the token symbol is in the wallet + async getTokenFromWallet(runtime: IAgentRuntime, tokenSymbol: string) { + try { + const items = await this.getTokensInWallet(runtime); + const token = items.find((item) => item.symbol === tokenSymbol); + + if (token) { + return token.address; + } else { + return null; + } + } catch (error) { + console.error("Error checking token in wallet:", error); + return null; + } + } + + async fetchTokenCodex(): Promise { + try { + const cacheKey = `token_${this.tokenAddress}`; + const cachedData = this.getCachedData(cacheKey); + if (cachedData) { + console.log( + `Returning cached token data for ${this.tokenAddress}.` + ); + return cachedData; + } + const query = ` + query Token($address: String!, $networkId: Int!) { + token(input: { address: $address, networkId: $networkId }) { + id + address + cmcId + decimals + name + symbol + totalSupply + isScam + info { + circulatingSupply + imageThumbUrl + } + explorerData { + blueCheckmark + description + tokenType + } + } + } + `; + + const variables = { + address: this.tokenAddress, + networkId: this.NETWORK_ID, // Replace with your network ID + }; + + const response = await fetch(this.GRAPHQL_ENDPOINT, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: settings.CODEX_API_KEY, + }, + body: JSON.stringify({ + query, + variables, + }), + }).then((res) => res.json()); + + const token = response.data?.data?.token; + + if (!token) { + throw new Error(`No data returned for token ${tokenAddress}`); + } + + this.setCachedData(cacheKey, token); + + return { + id: token.id, + address: token.address, + cmcId: token.cmcId, + decimals: token.decimals, + name: token.name, + symbol: token.symbol, + totalSupply: token.totalSupply, + circulatingSupply: token.info?.circulatingSupply, + imageThumbUrl: token.info?.imageThumbUrl, + blueCheckmark: token.explorerData?.blueCheckmark, + isScam: token.isScam ? true : false, + }; + } catch (error) { + console.error( + "Error fetching token data from Codex:", + error.message + ); + return {} as TokenCodex; + } + } + + async fetchPrices(): Promise { + try { + const cacheKey = "prices"; + const cachedData = this.getCachedData(cacheKey); + if (cachedData) { + console.log("Returning cached prices."); + return cachedData; + } + const { SOL, BTC, ETH } = PROVIDER_CONFIG.TOKEN_ADDRESSES; + const tokens = [SOL, BTC, ETH]; + const prices: Prices = { + solana: { usd: "0" }, + bitcoin: { usd: "0" }, + ethereum: { usd: "0" }, + }; + + for (const token of tokens) { + const response = await this.fetchWithRetry( + `${PROVIDER_CONFIG.BIRDEYE_API}/defi/price?address=${token}`, + { + headers: { + "x-chain": "solana", + }, + } + ); + + if (response?.data?.value) { + const price = response.data.value.toString(); + prices[ + token === SOL + ? "solana" + : token === BTC + ? "bitcoin" + : "ethereum" + ].usd = price; + } else { + console.warn(`No price data available for token: ${token}`); + } + } + this.setCachedData(cacheKey, prices); + return prices; + } catch (error) { + console.error("Error fetching prices:", error); + throw error; + } + } + async calculateBuyAmounts(): Promise { + const dexScreenerData = await this.fetchDexScreenerData(); + const prices = await this.fetchPrices(); + const solPrice = toBN(prices.solana.usd); + + if (!dexScreenerData || dexScreenerData.pairs.length === 0) { + return { none: 0, low: 0, medium: 0, high: 0 }; + } + + // Get the first pair + const pair = dexScreenerData.pairs[0]; + const { liquidity, marketCap } = pair; + if (!liquidity || !marketCap) { + return { none: 0, low: 0, medium: 0, high: 0 }; + } + + if (liquidity.usd === 0) { + return { none: 0, low: 0, medium: 0, high: 0 }; + } + if (marketCap < 100000) { + return { none: 0, low: 0, medium: 0, high: 0 }; + } + + // impact percentages based on liquidity + const impactPercentages = { + LOW: 0.01, // 1% of liquidity + MEDIUM: 0.05, // 5% of liquidity + HIGH: 0.1, // 10% of liquidity + }; + + // Calculate buy amounts in USD + const lowBuyAmountUSD = liquidity.usd * impactPercentages.LOW; + const mediumBuyAmountUSD = liquidity.usd * impactPercentages.MEDIUM; + const highBuyAmountUSD = liquidity.usd * impactPercentages.HIGH; + + // Convert each buy amount to SOL + const lowBuyAmountSOL = toBN(lowBuyAmountUSD).div(solPrice).toNumber(); + const mediumBuyAmountSOL = toBN(mediumBuyAmountUSD) + .div(solPrice) + .toNumber(); + const highBuyAmountSOL = toBN(highBuyAmountUSD) + .div(solPrice) + .toNumber(); + + return { + none: 0, + low: lowBuyAmountSOL, + medium: mediumBuyAmountSOL, + high: highBuyAmountSOL, + }; + } + + async fetchTokenSecurity(): Promise { + const cacheKey = `tokenSecurity_${this.tokenAddress}`; + const cachedData = this.getCachedData(cacheKey); + if (cachedData) { + console.log( + `Returning cached token security data for ${this.tokenAddress}.` + ); + return cachedData; + } + const url = `${PROVIDER_CONFIG.BIRDEYE_API}${PROVIDER_CONFIG.TOKEN_SECURITY_ENDPOINT}${this.tokenAddress}`; + const data = await this.fetchWithRetry(url); + + if (!data?.success || !data?.data) { + throw new Error("No token security data available"); + } + + const security: TokenSecurityData = { + ownerBalance: data.data.ownerBalance, + creatorBalance: data.data.creatorBalance, + ownerPercentage: data.data.ownerPercentage, + creatorPercentage: data.data.creatorPercentage, + top10HolderBalance: data.data.top10HolderBalance, + top10HolderPercent: data.data.top10HolderPercent, + }; + this.setCachedData(cacheKey, security); + console.log(`Token security data cached for ${this.tokenAddress}.`); + + return security; + } + + async fetchTokenTradeData(): Promise { + const cacheKey = `tokenTradeData_${this.tokenAddress}`; + const cachedData = this.getCachedData(cacheKey); + if (cachedData) { + console.log( + `Returning cached token trade data for ${this.tokenAddress}.` + ); + return cachedData; + } + + const url = `${PROVIDER_CONFIG.BIRDEYE_API}${PROVIDER_CONFIG.TOKEN_TRADE_DATA_ENDPOINT}${this.tokenAddress}`; + const options = { + method: "GET", + headers: { + accept: "application/json", + "X-API-KEY": settings.BIRDEYE_API_KEY || "", + }, + }; + + const data = await fetch(url, options) + .then((res) => res.json()) + .catch((err) => console.error(err)); + + if (!data?.success || !data?.data) { + throw new Error("No token trade data available"); + } + + const tradeData: TokenTradeData = { + address: data.data.address, + holder: data.data.holder, + market: data.data.market, + last_trade_unix_time: data.data.last_trade_unix_time, + last_trade_human_time: data.data.last_trade_human_time, + price: data.data.price, + history_30m_price: data.data.history_30m_price, + price_change_30m_percent: data.data.price_change_30m_percent, + history_1h_price: data.data.history_1h_price, + price_change_1h_percent: data.data.price_change_1h_percent, + history_2h_price: data.data.history_2h_price, + price_change_2h_percent: data.data.price_change_2h_percent, + history_4h_price: data.data.history_4h_price, + price_change_4h_percent: data.data.price_change_4h_percent, + history_6h_price: data.data.history_6h_price, + price_change_6h_percent: data.data.price_change_6h_percent, + history_8h_price: data.data.history_8h_price, + price_change_8h_percent: data.data.price_change_8h_percent, + history_12h_price: data.data.history_12h_price, + price_change_12h_percent: data.data.price_change_12h_percent, + history_24h_price: data.data.history_24h_price, + price_change_24h_percent: data.data.price_change_24h_percent, + unique_wallet_30m: data.data.unique_wallet_30m, + unique_wallet_history_30m: data.data.unique_wallet_history_30m, + unique_wallet_30m_change_percent: + data.data.unique_wallet_30m_change_percent, + unique_wallet_1h: data.data.unique_wallet_1h, + unique_wallet_history_1h: data.data.unique_wallet_history_1h, + unique_wallet_1h_change_percent: + data.data.unique_wallet_1h_change_percent, + unique_wallet_2h: data.data.unique_wallet_2h, + unique_wallet_history_2h: data.data.unique_wallet_history_2h, + unique_wallet_2h_change_percent: + data.data.unique_wallet_2h_change_percent, + unique_wallet_4h: data.data.unique_wallet_4h, + unique_wallet_history_4h: data.data.unique_wallet_history_4h, + unique_wallet_4h_change_percent: + data.data.unique_wallet_4h_change_percent, + unique_wallet_8h: data.data.unique_wallet_8h, + unique_wallet_history_8h: data.data.unique_wallet_history_8h, + unique_wallet_8h_change_percent: + data.data.unique_wallet_8h_change_percent, + unique_wallet_24h: data.data.unique_wallet_24h, + unique_wallet_history_24h: data.data.unique_wallet_history_24h, + unique_wallet_24h_change_percent: + data.data.unique_wallet_24h_change_percent, + trade_30m: data.data.trade_30m, + trade_history_30m: data.data.trade_history_30m, + trade_30m_change_percent: data.data.trade_30m_change_percent, + sell_30m: data.data.sell_30m, + sell_history_30m: data.data.sell_history_30m, + sell_30m_change_percent: data.data.sell_30m_change_percent, + buy_30m: data.data.buy_30m, + buy_history_30m: data.data.buy_history_30m, + buy_30m_change_percent: data.data.buy_30m_change_percent, + volume_30m: data.data.volume_30m, + volume_30m_usd: data.data.volume_30m_usd, + volume_history_30m: data.data.volume_history_30m, + volume_history_30m_usd: data.data.volume_history_30m_usd, + volume_30m_change_percent: data.data.volume_30m_change_percent, + volume_buy_30m: data.data.volume_buy_30m, + volume_buy_30m_usd: data.data.volume_buy_30m_usd, + volume_buy_history_30m: data.data.volume_buy_history_30m, + volume_buy_history_30m_usd: data.data.volume_buy_history_30m_usd, + volume_buy_30m_change_percent: + data.data.volume_buy_30m_change_percent, + volume_sell_30m: data.data.volume_sell_30m, + volume_sell_30m_usd: data.data.volume_sell_30m_usd, + volume_sell_history_30m: data.data.volume_sell_history_30m, + volume_sell_history_30m_usd: data.data.volume_sell_history_30m_usd, + volume_sell_30m_change_percent: + data.data.volume_sell_30m_change_percent, + trade_1h: data.data.trade_1h, + trade_history_1h: data.data.trade_history_1h, + trade_1h_change_percent: data.data.trade_1h_change_percent, + sell_1h: data.data.sell_1h, + sell_history_1h: data.data.sell_history_1h, + sell_1h_change_percent: data.data.sell_1h_change_percent, + buy_1h: data.data.buy_1h, + buy_history_1h: data.data.buy_history_1h, + buy_1h_change_percent: data.data.buy_1h_change_percent, + volume_1h: data.data.volume_1h, + volume_1h_usd: data.data.volume_1h_usd, + volume_history_1h: data.data.volume_history_1h, + volume_history_1h_usd: data.data.volume_history_1h_usd, + volume_1h_change_percent: data.data.volume_1h_change_percent, + volume_buy_1h: data.data.volume_buy_1h, + volume_buy_1h_usd: data.data.volume_buy_1h_usd, + volume_buy_history_1h: data.data.volume_buy_history_1h, + volume_buy_history_1h_usd: data.data.volume_buy_history_1h_usd, + volume_buy_1h_change_percent: + data.data.volume_buy_1h_change_percent, + volume_sell_1h: data.data.volume_sell_1h, + volume_sell_1h_usd: data.data.volume_sell_1h_usd, + volume_sell_history_1h: data.data.volume_sell_history_1h, + volume_sell_history_1h_usd: data.data.volume_sell_history_1h_usd, + volume_sell_1h_change_percent: + data.data.volume_sell_1h_change_percent, + trade_2h: data.data.trade_2h, + trade_history_2h: data.data.trade_history_2h, + trade_2h_change_percent: data.data.trade_2h_change_percent, + sell_2h: data.data.sell_2h, + sell_history_2h: data.data.sell_history_2h, + sell_2h_change_percent: data.data.sell_2h_change_percent, + buy_2h: data.data.buy_2h, + buy_history_2h: data.data.buy_history_2h, + buy_2h_change_percent: data.data.buy_2h_change_percent, + volume_2h: data.data.volume_2h, + volume_2h_usd: data.data.volume_2h_usd, + volume_history_2h: data.data.volume_history_2h, + volume_history_2h_usd: data.data.volume_history_2h_usd, + volume_2h_change_percent: data.data.volume_2h_change_percent, + volume_buy_2h: data.data.volume_buy_2h, + volume_buy_2h_usd: data.data.volume_buy_2h_usd, + volume_buy_history_2h: data.data.volume_buy_history_2h, + volume_buy_history_2h_usd: data.data.volume_buy_history_2h_usd, + volume_buy_2h_change_percent: + data.data.volume_buy_2h_change_percent, + volume_sell_2h: data.data.volume_sell_2h, + volume_sell_2h_usd: data.data.volume_sell_2h_usd, + volume_sell_history_2h: data.data.volume_sell_history_2h, + volume_sell_history_2h_usd: data.data.volume_sell_history_2h_usd, + volume_sell_2h_change_percent: + data.data.volume_sell_2h_change_percent, + trade_4h: data.data.trade_4h, + trade_history_4h: data.data.trade_history_4h, + trade_4h_change_percent: data.data.trade_4h_change_percent, + sell_4h: data.data.sell_4h, + sell_history_4h: data.data.sell_history_4h, + sell_4h_change_percent: data.data.sell_4h_change_percent, + buy_4h: data.data.buy_4h, + buy_history_4h: data.data.buy_history_4h, + buy_4h_change_percent: data.data.buy_4h_change_percent, + volume_4h: data.data.volume_4h, + volume_4h_usd: data.data.volume_4h_usd, + volume_history_4h: data.data.volume_history_4h, + volume_history_4h_usd: data.data.volume_history_4h_usd, + volume_4h_change_percent: data.data.volume_4h_change_percent, + volume_buy_4h: data.data.volume_buy_4h, + volume_buy_4h_usd: data.data.volume_buy_4h_usd, + volume_buy_history_4h: data.data.volume_buy_history_4h, + volume_buy_history_4h_usd: data.data.volume_buy_history_4h_usd, + volume_buy_4h_change_percent: + data.data.volume_buy_4h_change_percent, + volume_sell_4h: data.data.volume_sell_4h, + volume_sell_4h_usd: data.data.volume_sell_4h_usd, + volume_sell_history_4h: data.data.volume_sell_history_4h, + volume_sell_history_4h_usd: data.data.volume_sell_history_4h_usd, + volume_sell_4h_change_percent: + data.data.volume_sell_4h_change_percent, + trade_8h: data.data.trade_8h, + trade_history_8h: data.data.trade_history_8h, + trade_8h_change_percent: data.data.trade_8h_change_percent, + sell_8h: data.data.sell_8h, + sell_history_8h: data.data.sell_history_8h, + sell_8h_change_percent: data.data.sell_8h_change_percent, + buy_8h: data.data.buy_8h, + buy_history_8h: data.data.buy_history_8h, + buy_8h_change_percent: data.data.buy_8h_change_percent, + volume_8h: data.data.volume_8h, + volume_8h_usd: data.data.volume_8h_usd, + volume_history_8h: data.data.volume_history_8h, + volume_history_8h_usd: data.data.volume_history_8h_usd, + volume_8h_change_percent: data.data.volume_8h_change_percent, + volume_buy_8h: data.data.volume_buy_8h, + volume_buy_8h_usd: data.data.volume_buy_8h_usd, + volume_buy_history_8h: data.data.volume_buy_history_8h, + volume_buy_history_8h_usd: data.data.volume_buy_history_8h_usd, + volume_buy_8h_change_percent: + data.data.volume_buy_8h_change_percent, + volume_sell_8h: data.data.volume_sell_8h, + volume_sell_8h_usd: data.data.volume_sell_8h_usd, + volume_sell_history_8h: data.data.volume_sell_history_8h, + volume_sell_history_8h_usd: data.data.volume_sell_history_8h_usd, + volume_sell_8h_change_percent: + data.data.volume_sell_8h_change_percent, + trade_24h: data.data.trade_24h, + trade_history_24h: data.data.trade_history_24h, + trade_24h_change_percent: data.data.trade_24h_change_percent, + sell_24h: data.data.sell_24h, + sell_history_24h: data.data.sell_history_24h, + sell_24h_change_percent: data.data.sell_24h_change_percent, + buy_24h: data.data.buy_24h, + buy_history_24h: data.data.buy_history_24h, + buy_24h_change_percent: data.data.buy_24h_change_percent, + volume_24h: data.data.volume_24h, + volume_24h_usd: data.data.volume_24h_usd, + volume_history_24h: data.data.volume_history_24h, + volume_history_24h_usd: data.data.volume_history_24h_usd, + volume_24h_change_percent: data.data.volume_24h_change_percent, + volume_buy_24h: data.data.volume_buy_24h, + volume_buy_24h_usd: data.data.volume_buy_24h_usd, + volume_buy_history_24h: data.data.volume_buy_history_24h, + volume_buy_history_24h_usd: data.data.volume_buy_history_24h_usd, + volume_buy_24h_change_percent: + data.data.volume_buy_24h_change_percent, + volume_sell_24h: data.data.volume_sell_24h, + volume_sell_24h_usd: data.data.volume_sell_24h_usd, + volume_sell_history_24h: data.data.volume_sell_history_24h, + volume_sell_history_24h_usd: data.data.volume_sell_history_24h_usd, + volume_sell_24h_change_percent: + data.data.volume_sell_24h_change_percent, + }; + this.setCachedData(cacheKey, tradeData); + return tradeData; + } + + async fetchDexScreenerData(): Promise { + const cacheKey = `dexScreenerData_${this.tokenAddress}`; + const cachedData = this.getCachedData(cacheKey); + if (cachedData) { + console.log("Returning cached DexScreener data."); + return cachedData; + } + + const url = `https://api.dexscreener.com/latest/dex/search?q=${this.tokenAddress}`; + try { + console.log( + `Fetching DexScreener data for token: ${this.tokenAddress}` + ); + const data = await fetch(url) + .then((res) => res.json()) + .catch((err) => { + console.error(err); + }); + + if (!data || !data.pairs) { + throw new Error("No DexScreener data available"); + } + + const dexData: DexScreenerData = { + schemaVersion: data.schemaVersion, + pairs: data.pairs, + }; + + // Cache the result + this.setCachedData(cacheKey, dexData); + + return dexData; + } catch (error) { + console.error(`Error fetching DexScreener data:`, error); + return { + schemaVersion: "1.0.0", + pairs: [], + }; + } + } + + async searchDexScreenerData( + symbol: string + ): Promise { + const cacheKey = `dexScreenerData_search_${symbol}`; + const cachedData = await this.getCachedData(cacheKey); + if (cachedData) { + console.log("Returning cached search DexScreener data."); + return this.getHighestLiquidityPair(cachedData); + } + + const url = `https://api.dexscreener.com/latest/dex/search?q=${symbol}`; + try { + console.log(`Fetching DexScreener data for symbol: ${symbol}`); + const data = await fetch(url) + .then((res) => res.json()) + .catch((err) => { + console.error(err); + return null; + }); + + if (!data || !data.pairs || data.pairs.length === 0) { + throw new Error("No DexScreener data available"); + } + + const dexData: DexScreenerData = { + schemaVersion: data.schemaVersion, + pairs: data.pairs, + }; + + // Cache the result + this.setCachedData(cacheKey, dexData); + + // Return the pair with the highest liquidity and market cap + return this.getHighestLiquidityPair(dexData); + } catch (error) { + console.error(`Error fetching DexScreener data:`, error); + return null; + } + } + getHighestLiquidityPair(dexData: DexScreenerData): DexScreenerPair | null { + if (dexData.pairs.length === 0) { + return null; + } + + // Sort pairs by both liquidity and market cap to get the highest one + return dexData.pairs.sort((a, b) => { + const liquidityDiff = b.liquidity.usd - a.liquidity.usd; + if (liquidityDiff !== 0) { + return liquidityDiff; // Higher liquidity comes first + } + return b.marketCap - a.marketCap; // If liquidity is equal, higher market cap comes first + })[0]; + } + + async analyzeHolderDistribution( + tradeData: TokenTradeData + ): Promise { + // Define the time intervals to consider (e.g., 30m, 1h, 2h) + const intervals = [ + { + period: "30m", + change: tradeData.unique_wallet_30m_change_percent, + }, + { period: "1h", change: tradeData.unique_wallet_1h_change_percent }, + { period: "2h", change: tradeData.unique_wallet_2h_change_percent }, + { period: "4h", change: tradeData.unique_wallet_4h_change_percent }, + { period: "8h", change: tradeData.unique_wallet_8h_change_percent }, + { + period: "24h", + change: tradeData.unique_wallet_24h_change_percent, + }, + ]; + + // Calculate the average change percentage + const validChanges = intervals + .map((interval) => interval.change) + .filter( + (change) => change !== null && change !== undefined + ) as number[]; + + if (validChanges.length === 0) { + return "stable"; + } + + const averageChange = + validChanges.reduce((acc, curr) => acc + curr, 0) / + validChanges.length; + + const increaseThreshold = 10; // e.g., average change > 10% + const decreaseThreshold = -10; // e.g., average change < -10% + + if (averageChange > increaseThreshold) { + return "increasing"; + } else if (averageChange < decreaseThreshold) { + return "decreasing"; + } else { + return "stable"; + } + } + + async fetchHolderList(): Promise { + const cacheKey = `holderList_${this.tokenAddress}`; + const cachedData = this.getCachedData(cacheKey); + if (cachedData) { + console.log("Returning cached holder list."); + return cachedData; + } + + const allHoldersMap = new Map(); + let page = 1; + const limit = 1000; + let cursor; + //HELIOUS_API_KEY needs to be added + const url = `https://mainnet.helius-rpc.com/?api-key=${settings.HELIUS_API_KEY || ""}`; + console.log({ url }); + + try { + while (true) { + const params = { + limit: limit, + displayOptions: {}, + mint: this.tokenAddress, + cursor: cursor, + }; + if (cursor != undefined) { + params.cursor = cursor; + } + console.log(`Fetching holders - Page ${page}`); + if (page > 2) { + break; + } + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: "helius-test", + method: "getTokenAccounts", + params: params, + }), + }); + + const data = await response.json(); + + if ( + !data || + !data.result || + !data.result.token_accounts || + data.result.token_accounts.length === 0 + ) { + console.log( + `No more holders found. Total pages fetched: ${page - 1}` + ); + break; + } + + console.log( + `Processing ${data.result.token_accounts.length} holders from page ${page}` + ); + + data.result.token_accounts.forEach((account: any) => { + const owner = account.owner; + const balance = parseFloat(account.amount); + + if (allHoldersMap.has(owner)) { + allHoldersMap.set( + owner, + allHoldersMap.get(owner)! + balance + ); + } else { + allHoldersMap.set(owner, balance); + } + }); + cursor = data.result.cursor; + page++; + } + + const holders: HolderData[] = Array.from( + allHoldersMap.entries() + ).map(([address, balance]) => ({ + address, + balance: balance.toString(), + })); + + console.log(`Total unique holders fetched: ${holders.length}`); + + // Cache the result + this.setCachedData(cacheKey, holders); + + return holders; + } catch (error) { + console.error("Error fetching holder list from Helius:", error); + throw new Error("Failed to fetch holder list from Helius."); + } + } + + async filterHighValueHolders( + tradeData: TokenTradeData + ): Promise> { + const holdersData = await this.fetchHolderList(); + + const tokenPriceUsd = toBN(tradeData.price); + + const highValueHolders = holdersData + .filter((holder) => { + const balanceUsd = toBN(holder.balance).multipliedBy( + tokenPriceUsd + ); + return balanceUsd.isGreaterThan(5); + }) + .map((holder) => ({ + holderAddress: holder.address, + balanceUsd: toBN(holder.balance) + .multipliedBy(tokenPriceUsd) + .toFixed(2), + })); + + return highValueHolders; + } + + async checkRecentTrades(tradeData: TokenTradeData): Promise { + return toBN(tradeData.volume_24h_usd).isGreaterThan(0); + } + + async countHighSupplyHolders( + securityData: TokenSecurityData + ): Promise { + try { + const ownerBalance = toBN(securityData.ownerBalance); + const totalSupply = ownerBalance.plus(securityData.creatorBalance); + + const highSupplyHolders = await this.fetchHolderList(); + const highSupplyHoldersCount = highSupplyHolders.filter( + (holder) => { + const balance = toBN(holder.balance); + return balance.dividedBy(totalSupply).isGreaterThan(0.02); + } + ).length; + return highSupplyHoldersCount; + } catch (error) { + console.error("Error counting high supply holders:", error); + return 0; + } + } + + async getProcessedTokenData(): Promise { + try { + console.log( + `Fetching security data for token: ${this.tokenAddress}` + ); + const security = await this.fetchTokenSecurity(); + + const tokenCodex = await this.fetchTokenCodex(); + + console.log(`Fetching trade data for token: ${this.tokenAddress}`); + const tradeData = await this.fetchTokenTradeData(); + + console.log( + `Fetching DexScreener data for token: ${this.tokenAddress}` + ); + const dexData = await this.fetchDexScreenerData(); + + console.log( + `Analyzing holder distribution for token: ${this.tokenAddress}` + ); + const holderDistributionTrend = + await this.analyzeHolderDistribution(tradeData); + + console.log( + `Filtering high-value holders for token: ${this.tokenAddress}` + ); + const highValueHolders = + await this.filterHighValueHolders(tradeData); + + console.log( + `Checking recent trades for token: ${this.tokenAddress}` + ); + const recentTrades = await this.checkRecentTrades(tradeData); + + console.log( + `Counting high-supply holders for token: ${this.tokenAddress}` + ); + const highSupplyHoldersCount = + await this.countHighSupplyHolders(security); + + console.log( + `Determining DexScreener listing status for token: ${this.tokenAddress}` + ); + const isDexScreenerListed = dexData.pairs.length > 0; + const isDexScreenerPaid = dexData.pairs.some( + (pair) => pair.boosts && pair.boosts.active > 0 + ); + + const processedData: ProcessedTokenData = { + security, + tradeData, + holderDistributionTrend, + highValueHolders, + recentTrades, + highSupplyHoldersCount, + dexScreenerData: dexData, + isDexScreenerListed, + isDexScreenerPaid, + tokenCodex, + }; + + // console.log("Processed token data:", processedData); + return processedData; + } catch (error) { + console.error("Error processing token data:", error); + throw error; + } + } + + async shouldTradeToken(): Promise { + try { + const tokenData = await this.getProcessedTokenData(); + const { tradeData, security, dexScreenerData } = tokenData; + const { ownerBalance, creatorBalance } = security; + const { liquidity, marketCap } = dexScreenerData.pairs[0]; + const liquidityUsd = toBN(liquidity.usd); + const marketCapUsd = toBN(marketCap); + const totalSupply = toBN(ownerBalance).plus(creatorBalance); + const _ownerPercentage = toBN(ownerBalance).dividedBy(totalSupply); + const _creatorPercentage = + toBN(creatorBalance).dividedBy(totalSupply); + const top10HolderPercent = toBN(tradeData.volume_24h_usd).dividedBy( + totalSupply + ); + const priceChange24hPercent = toBN( + tradeData.price_change_24h_percent + ); + const priceChange12hPercent = toBN( + tradeData.price_change_12h_percent + ); + const uniqueWallet24h = tradeData.unique_wallet_24h; + const volume24hUsd = toBN(tradeData.volume_24h_usd); + const volume24hUsdThreshold = 1000; + const priceChange24hPercentThreshold = 10; + const priceChange12hPercentThreshold = 5; + const top10HolderPercentThreshold = 0.05; + const uniqueWallet24hThreshold = 100; + const isTop10Holder = top10HolderPercent.gte( + top10HolderPercentThreshold + ); + const isVolume24h = volume24hUsd.gte(volume24hUsdThreshold); + const isPriceChange24h = priceChange24hPercent.gte( + priceChange24hPercentThreshold + ); + const isPriceChange12h = priceChange12hPercent.gte( + priceChange12hPercentThreshold + ); + const isUniqueWallet24h = + uniqueWallet24h >= uniqueWallet24hThreshold; + const isLiquidityTooLow = liquidityUsd.lt(1000); + const isMarketCapTooLow = marketCapUsd.lt(100000); + return ( + isTop10Holder || + isVolume24h || + isPriceChange24h || + isPriceChange12h || + isUniqueWallet24h || + isLiquidityTooLow || + isMarketCapTooLow + ); + } catch (error) { + console.error("Error processing token data:", error); + throw error; + } + } + + formatTokenData(data: ProcessedTokenData): string { + let output = `**Token Security and Trade Report**\n`; + output += `Token Address: ${this.tokenAddress}\n\n`; + + // Security Data + 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.tradeData.holder}\n`; + output += `- Unique Wallets (24h): ${data.tradeData.unique_wallet_24h}\n`; + output += `- Price Change (24h): ${data.tradeData.price_change_24h_percent}%\n`; + output += `- Price Change (12h): ${data.tradeData.price_change_12h_percent}%\n`; + output += `- Volume (24h USD): $${toBN(data.tradeData.volume_24h_usd).toFixed(2)}\n`; + output += `- Current Price: $${toBN(data.tradeData.price).toFixed(2)}\n\n`; + + // Holder Distribution Trend + output += `**Holder Distribution Trend:** ${data.holderDistributionTrend}\n\n`; + + // High-Value Holders + output += `**High-Value Holders (>$5 USD):**\n`; + if (data.highValueHolders.length === 0) { + output += `- No high-value holders found or data not available.\n`; + } else { + data.highValueHolders.forEach((holder) => { + output += `- ${holder.holderAddress}: $${holder.balanceUsd}\n`; + }); + } + output += `\n`; + + // Recent Trades + output += `**Recent Trades (Last 24h):** ${data.recentTrades ? "Yes" : "No"}\n\n`; + + // High-Supply Holders + output += `**Holders with >2% Supply:** ${data.highSupplyHoldersCount}\n\n`; + + // DexScreener Status + output += `**DexScreener Listing:** ${data.isDexScreenerListed ? "Yes" : "No"}\n`; + if (data.isDexScreenerListed) { + output += `- Listing Type: ${data.isDexScreenerPaid ? "Paid" : "Free"}\n`; + output += `- Number of DexPairs: ${data.dexScreenerData.pairs.length}\n\n`; + output += `**DexScreener Pairs:**\n`; + data.dexScreenerData.pairs.forEach((pair, index) => { + output += `\n**Pair ${index + 1}:**\n`; + output += `- DEX: ${pair.dexId}\n`; + output += `- URL: ${pair.url}\n`; + output += `- Price USD: $${toBN(pair.priceUsd).toFixed(6)}\n`; + output += `- Volume (24h USD): $${toBN(pair.volume.h24).toFixed(2)}\n`; + output += `- Boosts Active: ${pair.boosts && pair.boosts.active}\n`; + output += `- Liquidity USD: $${toBN(pair.liquidity.usd).toFixed(2)}\n`; + }); + } + output += `\n`; + + console.log("Formatted token data:", output); + return output; + } + + async getFormattedTokenReport(): Promise { + try { + console.log("Generating formatted token report..."); + const processedData = await this.getProcessedTokenData(); + return this.formatTokenData(processedData); + } catch (error) { + console.error("Error generating token report:", error); + return "Unable to fetch token information. Please try again later."; + } + } +} + +const tokenAddress = PROVIDER_CONFIG.TOKEN_ADDRESSES.Example; + +const connection = new Connection(PROVIDER_CONFIG.DEFAULT_RPC); +const tokenProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + _message: Memory, + _state?: State + ): Promise => { + try { + const { publicKey } = await getWalletKey(runtime, false); + + const walletProvider = new WalletProvider(connection, publicKey); + + const provider = new TokenProvider( + tokenAddress, + walletProvider, + runtime.cacheManager + ); + + return provider.getFormattedTokenReport(); + } catch (error) { + console.error("Error fetching token data:", error); + return "Unable to fetch token information. Please try again later."; + } + }, +}; + +export { tokenProvider }; diff --git a/packages/plugin-solana-agentkit/src/providers/tokenUtils.ts b/packages/plugin-solana-agentkit/src/providers/tokenUtils.ts new file mode 100644 index 0000000000..034dddc299 --- /dev/null +++ b/packages/plugin-solana-agentkit/src/providers/tokenUtils.ts @@ -0,0 +1,72 @@ +import { getAccount, getAssociatedTokenAddress } from "@solana/spl-token"; +import { Connection, PublicKey } from "@solana/web3.js"; + +export async function getTokenPriceInSol(tokenSymbol: string): Promise { + const response = await fetch( + `https://price.jup.ag/v6/price?ids=${tokenSymbol}` + ); + const data = await response.json(); + return data.data[tokenSymbol].price; +} + +async function getTokenBalance( + connection: Connection, + walletPublicKey: PublicKey, + tokenMintAddress: PublicKey +): Promise { + const tokenAccountAddress = await getAssociatedTokenAddress( + tokenMintAddress, + walletPublicKey + ); + + try { + const tokenAccount = await getAccount(connection, tokenAccountAddress); + const tokenAmount = tokenAccount.amount as unknown as number; + return tokenAmount; + } catch (error) { + console.error( + `Error retrieving balance for token: ${tokenMintAddress.toBase58()}`, + error + ); + return 0; + } +} + +async function getTokenBalances( + connection: Connection, + walletPublicKey: PublicKey +): Promise<{ [tokenName: string]: number }> { + const tokenBalances: { [tokenName: string]: number } = {}; + + // Add the token mint addresses you want to retrieve balances for + const tokenMintAddresses = [ + new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), // USDC + new PublicKey("So11111111111111111111111111111111111111112"), // SOL + // Add more token mint addresses as needed + ]; + + for (const mintAddress of tokenMintAddresses) { + const tokenName = getTokenName(mintAddress); + const balance = await getTokenBalance( + connection, + walletPublicKey, + mintAddress + ); + tokenBalances[tokenName] = balance; + } + + return tokenBalances; +} + +function getTokenName(mintAddress: PublicKey): string { + // Implement a mapping of mint addresses to token names + const tokenNameMap: { [mintAddress: string]: string } = { + EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v: "USDC", + So11111111111111111111111111111111111111112: "SOL", + // Add more token mint addresses and their corresponding names + }; + + return tokenNameMap[mintAddress.toBase58()] || "Unknown Token"; +} + +export { getTokenBalance, getTokenBalances }; diff --git a/packages/plugin-solana-agentkit/src/providers/trustScoreProvider.ts b/packages/plugin-solana-agentkit/src/providers/trustScoreProvider.ts new file mode 100644 index 0000000000..931cd9b44d --- /dev/null +++ b/packages/plugin-solana-agentkit/src/providers/trustScoreProvider.ts @@ -0,0 +1,740 @@ +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"; +import { + TrustScoreDatabase, + RecommenderMetrics, + TokenPerformance, + TradePerformance, + TokenRecommendation, +} from "@elizaos/plugin-trustdb"; +import { settings } from "@elizaos/core"; +import { IAgentRuntime, Memory, Provider, State } from "@elizaos/core"; +import { v4 as uuidv4 } from "uuid"; + +const Wallet = settings.MAIN_WALLET_ADDRESS; +interface TradeData { + buy_amount: number; + is_simulation: boolean; +} +interface sellDetails { + sell_amount: number; + sell_recommender_id: string | null; +} +interface _RecommendationGroup { + recommendation: any; + trustScore: number; +} + +interface RecommenderData { + recommenderId: string; + trustScore: number; + riskScore: number; + consistencyScore: number; + recommenderMetrics: RecommenderMetrics; +} + +interface TokenRecommendationSummary { + tokenAddress: string; + averageTrustScore: number; + averageRiskScore: number; + averageConsistencyScore: number; + recommenders: RecommenderData[]; +} +export class TrustScoreManager { + private tokenProvider: TokenProvider; + private trustScoreDb: TrustScoreDatabase; + private simulationSellingService: SimulationSellingService; + private connection: Connection; + private baseMint: PublicKey; + private DECAY_RATE = 0.95; + private MAX_DECAY_DAYS = 30; + private backend; + private backendToken; + constructor( + runtime: IAgentRuntime, + tokenProvider: TokenProvider, + trustScoreDb: TrustScoreDatabase + ) { + this.tokenProvider = tokenProvider; + this.trustScoreDb = trustScoreDb; + this.connection = new Connection(runtime.getSetting("RPC_URL")); + this.baseMint = new PublicKey( + runtime.getSetting("BASE_MINT") || + "So11111111111111111111111111111111111111112" + ); + this.backend = runtime.getSetting("BACKEND_URL"); + this.backendToken = runtime.getSetting("BACKEND_TOKEN"); + this.simulationSellingService = new SimulationSellingService( + runtime, + this.trustScoreDb + ); + } + + //getRecommenederBalance + async getRecommenederBalance(recommenderWallet: string): Promise { + try { + const tokenAta = await getAssociatedTokenAddress( + new PublicKey(recommenderWallet), + this.baseMint + ); + const tokenBalInfo = + await this.connection.getTokenAccountBalance(tokenAta); + const tokenBalance = tokenBalInfo.value.amount; + const balance = parseFloat(tokenBalance); + return balance; + } catch (error) { + console.error("Error fetching balance", error); + return 0; + } + } + + /** + * Generates and saves trust score based on processed token data and user recommendations. + * @param tokenAddress The address of the token to analyze. + * @param recommenderId The UUID of the recommender. + * @returns An object containing TokenPerformance and RecommenderMetrics. + */ + async generateTrustScore( + tokenAddress: string, + recommenderId: string, + recommenderWallet: string + ): Promise<{ + tokenPerformance: TokenPerformance; + recommenderMetrics: RecommenderMetrics; + }> { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + console.log(`Fetched processed token data for token: ${tokenAddress}`); + + const recommenderMetrics = + await this.trustScoreDb.getRecommenderMetrics(recommenderId); + + const isRapidDump = await this.isRapidDump(tokenAddress); + const sustainedGrowth = await this.sustainedGrowth(tokenAddress); + const suspiciousVolume = await this.suspiciousVolume(tokenAddress); + const balance = await this.getRecommenederBalance(recommenderWallet); + const virtualConfidence = balance / 1000000; // TODO: create formula to calculate virtual confidence based on user balance + const lastActive = recommenderMetrics.lastActiveDate; + const now = new Date(); + const inactiveDays = Math.floor( + (now.getTime() - lastActive.getTime()) / (1000 * 60 * 60 * 24) + ); + const decayFactor = Math.pow( + this.DECAY_RATE, + Math.min(inactiveDays, this.MAX_DECAY_DAYS) + ); + const decayedScore = recommenderMetrics.trustScore * decayFactor; + const validationTrustScore = + this.trustScoreDb.calculateValidationTrust(tokenAddress); + + return { + tokenPerformance: { + tokenAddress: + processedData.dexScreenerData.pairs[0]?.baseToken.address || + "", + priceChange24h: + processedData.tradeData.price_change_24h_percent, + volumeChange24h: processedData.tradeData.volume_24h, + trade_24h_change: + processedData.tradeData.trade_24h_change_percent, + liquidity: + processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, + liquidityChange24h: 0, + holderChange24h: + processedData.tradeData.unique_wallet_24h_change_percent, + rugPull: false, + isScam: processedData.tokenCodex.isScam, + marketCapChange24h: 0, + sustainedGrowth: sustainedGrowth, + rapidDump: isRapidDump, + suspiciousVolume: suspiciousVolume, + validationTrust: validationTrustScore, + balance: balance, + initialMarketCap: + processedData.dexScreenerData.pairs[0]?.marketCap || 0, + lastUpdated: new Date(), + symbol: "", + }, + recommenderMetrics: { + recommenderId: recommenderId, + trustScore: recommenderMetrics.trustScore, + totalRecommendations: recommenderMetrics.totalRecommendations, + successfulRecs: recommenderMetrics.successfulRecs, + avgTokenPerformance: recommenderMetrics.avgTokenPerformance, + riskScore: recommenderMetrics.riskScore, + consistencyScore: recommenderMetrics.consistencyScore, + virtualConfidence: virtualConfidence, + lastActiveDate: now, + trustDecay: decayedScore, + lastUpdated: new Date(), + }, + }; + } + + async updateRecommenderMetrics( + recommenderId: string, + tokenPerformance: TokenPerformance, + recommenderWallet: string + ): Promise { + const recommenderMetrics = + await this.trustScoreDb.getRecommenderMetrics(recommenderId); + + const totalRecommendations = + recommenderMetrics.totalRecommendations + 1; + const successfulRecs = tokenPerformance.rugPull + ? recommenderMetrics.successfulRecs + : recommenderMetrics.successfulRecs + 1; + const avgTokenPerformance = + (recommenderMetrics.avgTokenPerformance * + recommenderMetrics.totalRecommendations + + tokenPerformance.priceChange24h) / + totalRecommendations; + + const overallTrustScore = this.calculateTrustScore( + tokenPerformance, + recommenderMetrics + ); + const riskScore = this.calculateOverallRiskScore( + tokenPerformance, + recommenderMetrics + ); + const consistencyScore = this.calculateConsistencyScore( + tokenPerformance, + recommenderMetrics + ); + + const balance = await this.getRecommenederBalance(recommenderWallet); + const virtualConfidence = balance / 1000000; // TODO: create formula to calculate virtual confidence based on user balance + const lastActive = recommenderMetrics.lastActiveDate; + const now = new Date(); + const inactiveDays = Math.floor( + (now.getTime() - lastActive.getTime()) / (1000 * 60 * 60 * 24) + ); + const decayFactor = Math.pow( + this.DECAY_RATE, + Math.min(inactiveDays, this.MAX_DECAY_DAYS) + ); + const decayedScore = recommenderMetrics.trustScore * decayFactor; + + const newRecommenderMetrics: RecommenderMetrics = { + recommenderId: recommenderId, + trustScore: overallTrustScore, + totalRecommendations: totalRecommendations, + successfulRecs: successfulRecs, + avgTokenPerformance: avgTokenPerformance, + riskScore: riskScore, + consistencyScore: consistencyScore, + virtualConfidence: virtualConfidence, + lastActiveDate: new Date(), + trustDecay: decayedScore, + lastUpdated: new Date(), + }; + + await this.trustScoreDb.updateRecommenderMetrics(newRecommenderMetrics); + } + + calculateTrustScore( + tokenPerformance: TokenPerformance, + recommenderMetrics: RecommenderMetrics + ): number { + const riskScore = this.calculateRiskScore(tokenPerformance); + const consistencyScore = this.calculateConsistencyScore( + tokenPerformance, + recommenderMetrics + ); + + return (riskScore + consistencyScore) / 2; + } + + calculateOverallRiskScore( + tokenPerformance: TokenPerformance, + recommenderMetrics: RecommenderMetrics + ) { + const riskScore = this.calculateRiskScore(tokenPerformance); + const consistencyScore = this.calculateConsistencyScore( + tokenPerformance, + recommenderMetrics + ); + + return (riskScore + consistencyScore) / 2; + } + + calculateRiskScore(tokenPerformance: TokenPerformance): number { + let riskScore = 0; + if (tokenPerformance.rugPull) { + riskScore += 10; + } + if (tokenPerformance.isScam) { + riskScore += 10; + } + if (tokenPerformance.rapidDump) { + riskScore += 5; + } + if (tokenPerformance.suspiciousVolume) { + riskScore += 5; + } + return riskScore; + } + + calculateConsistencyScore( + tokenPerformance: TokenPerformance, + recommenderMetrics: RecommenderMetrics + ): number { + const avgTokenPerformance = recommenderMetrics.avgTokenPerformance; + const priceChange24h = tokenPerformance.priceChange24h; + + return Math.abs(priceChange24h - avgTokenPerformance); + } + + async suspiciousVolume(tokenAddress: string): Promise { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + const unique_wallet_24h = processedData.tradeData.unique_wallet_24h; + const volume_24h = processedData.tradeData.volume_24h; + const suspiciousVolume = unique_wallet_24h / volume_24h > 0.5; + console.log(`Fetched processed token data for token: ${tokenAddress}`); + return suspiciousVolume; + } + + async sustainedGrowth(tokenAddress: string): Promise { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + console.log(`Fetched processed token data for token: ${tokenAddress}`); + + return processedData.tradeData.volume_24h_change_percent > 50; + } + + async isRapidDump(tokenAddress: string): Promise { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + console.log(`Fetched processed token data for token: ${tokenAddress}`); + + return processedData.tradeData.trade_24h_change_percent < -50; + } + + async checkTrustScore(tokenAddress: string): Promise { + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + console.log(`Fetched processed token data for token: ${tokenAddress}`); + + return { + ownerBalance: processedData.security.ownerBalance, + creatorBalance: processedData.security.creatorBalance, + ownerPercentage: processedData.security.ownerPercentage, + creatorPercentage: processedData.security.creatorPercentage, + top10HolderBalance: processedData.security.top10HolderBalance, + top10HolderPercent: processedData.security.top10HolderPercent, + }; + } + + /** + * Creates a TradePerformance object based on token data and recommender. + * @param tokenAddress The address of the token. + * @param recommenderId The UUID of the recommender. + * @param data ProcessedTokenData. + * @returns TradePerformance object. + */ + async createTradePerformance( + runtime: IAgentRuntime, + tokenAddress: string, + recommenderId: string, + data: TradeData + ): Promise { + const recommender = + await this.trustScoreDb.getOrCreateRecommenderWithTelegramId( + recommenderId + ); + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + const wallet = new WalletProvider( + this.connection, + new PublicKey(Wallet!) + ); + + let tokensBalance = 0; + const prices = await wallet.fetchPrices(runtime); + const solPrice = prices.solana.usd; + const buySol = data.buy_amount / parseFloat(solPrice); + const buy_value_usd = data.buy_amount * processedData.tradeData.price; + const token = await this.tokenProvider.fetchTokenTradeData(); + const tokenCodex = await this.tokenProvider.fetchTokenCodex(); + const tokenPrice = token.price; + tokensBalance = buy_value_usd / tokenPrice; + + const creationData = { + token_address: tokenAddress, + recommender_id: recommender.id, + buy_price: processedData.tradeData.price, + sell_price: 0, + buy_timeStamp: new Date().toISOString(), + sell_timeStamp: "", + buy_amount: data.buy_amount, + sell_amount: 0, + buy_sol: buySol, + received_sol: 0, + buy_value_usd: buy_value_usd, + sell_value_usd: 0, + profit_usd: 0, + profit_percent: 0, + buy_market_cap: + processedData.dexScreenerData.pairs[0]?.marketCap || 0, + sell_market_cap: 0, + market_cap_change: 0, + buy_liquidity: + processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, + sell_liquidity: 0, + liquidity_change: 0, + last_updated: new Date().toISOString(), + rapidDump: false, + }; + this.trustScoreDb.addTradePerformance(creationData, data.is_simulation); + // generate unique uuid for each TokenRecommendation + const tokenUUId = uuidv4(); + const tokenRecommendation: TokenRecommendation = { + id: tokenUUId, + recommenderId: recommenderId, + tokenAddress: tokenAddress, + timestamp: new Date(), + initialMarketCap: + processedData.dexScreenerData.pairs[0]?.marketCap || 0, + initialLiquidity: + processedData.dexScreenerData.pairs[0]?.liquidity?.usd || 0, + initialPrice: processedData.tradeData.price, + }; + this.trustScoreDb.addTokenRecommendation(tokenRecommendation); + + this.trustScoreDb.upsertTokenPerformance({ + tokenAddress: tokenAddress, + symbol: processedData.tokenCodex.symbol, + priceChange24h: processedData.tradeData.price_change_24h_percent, + volumeChange24h: processedData.tradeData.volume_24h, + trade_24h_change: processedData.tradeData.trade_24h_change_percent, + liquidity: + processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, + liquidityChange24h: 0, + holderChange24h: + processedData.tradeData.unique_wallet_24h_change_percent, + rugPull: false, + isScam: tokenCodex.isScam, + marketCapChange24h: 0, + sustainedGrowth: false, + rapidDump: false, + suspiciousVolume: false, + validationTrust: 0, + balance: tokensBalance, + initialMarketCap: + processedData.dexScreenerData.pairs[0]?.marketCap || 0, + lastUpdated: new Date(), + }); + + if (data.is_simulation) { + // If the trade is a simulation update the balance + this.trustScoreDb.updateTokenBalance(tokenAddress, tokensBalance); + // generate some random hash for simulations + const hash = Math.random().toString(36).substring(7); + const transaction = { + tokenAddress: tokenAddress, + type: "buy" as "buy" | "sell", + transactionHash: hash, + amount: data.buy_amount, + price: processedData.tradeData.price, + isSimulation: true, + timestamp: new Date().toISOString(), + }; + this.trustScoreDb.addTransaction(transaction); + } + this.simulationSellingService.processTokenPerformance( + tokenAddress, + recommenderId + ); + // api call to update trade performance + this.createTradeInBe(tokenAddress, recommenderId, data); + return creationData; + } + + async delay(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + async createTradeInBe( + tokenAddress: string, + recommenderId: string, + data: TradeData, + retries = 3, + delayMs = 2000 + ) { + for (let attempt = 1; attempt <= retries; attempt++) { + try { + await fetch( + `${this.backend}/api/updaters/createTradePerformance`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.backendToken}`, + }, + body: JSON.stringify({ + tokenAddress: tokenAddress, + tradeData: data, + recommenderId: recommenderId, + }), + } + ); + // If the request is successful, exit the loop + return; + } catch (error) { + console.error( + `Attempt ${attempt} failed: Error creating trade in backend`, + error + ); + if (attempt < retries) { + console.log(`Retrying in ${delayMs} ms...`); + await this.delay(delayMs); // Wait for the specified delay before retrying + } else { + console.error("All attempts failed."); + } + } + } + } + + /** + * Updates a trade with sell details. + * @param tokenAddress The address of the token. + * @param recommenderId The UUID of the recommender. + * @param buyTimeStamp The timestamp when the buy occurred. + * @param sellDetails An object containing sell-related details. + * @param isSimulation Whether the trade is a simulation. If true, updates in simulation_trade; otherwise, in trade. + * @returns boolean indicating success. + */ + + async updateSellDetails( + runtime: IAgentRuntime, + tokenAddress: string, + recommenderId: string, + sellTimeStamp: string, + sellDetails: sellDetails, + isSimulation: boolean + ) { + const recommender = + await this.trustScoreDb.getOrCreateRecommenderWithTelegramId( + recommenderId + ); + const processedData: ProcessedTokenData = + await this.tokenProvider.getProcessedTokenData(); + const wallet = new WalletProvider( + this.connection, + new PublicKey(Wallet!) + ); + const prices = await wallet.fetchPrices(runtime); + const solPrice = prices.solana.usd; + const sellSol = sellDetails.sell_amount / parseFloat(solPrice); + const sell_value_usd = + sellDetails.sell_amount * processedData.tradeData.price; + const trade = await this.trustScoreDb.getLatestTradePerformance( + tokenAddress, + recommender.id, + isSimulation + ); + const buyTimeStamp = trade.buy_timeStamp; + const marketCap = + processedData.dexScreenerData.pairs[0]?.marketCap || 0; + const liquidity = + processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0; + const sell_price = processedData.tradeData.price; + const profit_usd = sell_value_usd - trade.buy_value_usd; + const profit_percent = (profit_usd / trade.buy_value_usd) * 100; + + const market_cap_change = marketCap - trade.buy_market_cap; + const liquidity_change = liquidity - trade.buy_liquidity; + + const isRapidDump = await this.isRapidDump(tokenAddress); + + const sellDetailsData = { + sell_price: sell_price, + sell_timeStamp: sellTimeStamp, + sell_amount: sellDetails.sell_amount, + received_sol: sellSol, + sell_value_usd: sell_value_usd, + profit_usd: profit_usd, + profit_percent: profit_percent, + sell_market_cap: marketCap, + market_cap_change: market_cap_change, + sell_liquidity: liquidity, + liquidity_change: liquidity_change, + rapidDump: isRapidDump, + sell_recommender_id: sellDetails.sell_recommender_id || null, + }; + this.trustScoreDb.updateTradePerformanceOnSell( + tokenAddress, + recommender.id, + buyTimeStamp, + sellDetailsData, + isSimulation + ); + if (isSimulation) { + // If the trade is a simulation update the balance + const oldBalance = this.trustScoreDb.getTokenBalance(tokenAddress); + const tokenBalance = oldBalance - sellDetails.sell_amount; + this.trustScoreDb.updateTokenBalance(tokenAddress, tokenBalance); + // generate some random hash for simulations + const hash = Math.random().toString(36).substring(7); + const transaction = { + tokenAddress: tokenAddress, + type: "sell" as "buy" | "sell", + transactionHash: hash, + amount: sellDetails.sell_amount, + price: processedData.tradeData.price, + isSimulation: true, + timestamp: new Date().toISOString(), + }; + this.trustScoreDb.addTransaction(transaction); + } + + return sellDetailsData; + } + + // get all recommendations + async getRecommendations( + startDate: Date, + endDate: Date + ): Promise> { + const recommendations = this.trustScoreDb.getRecommendationsByDateRange( + startDate, + endDate + ); + + // Group recommendations by tokenAddress + const groupedRecommendations = recommendations.reduce( + (acc, recommendation) => { + const { tokenAddress } = recommendation; + if (!acc[tokenAddress]) acc[tokenAddress] = []; + acc[tokenAddress].push(recommendation); + return acc; + }, + {} as Record> + ); + + const result = Object.keys(groupedRecommendations).map( + (tokenAddress) => { + const tokenRecommendations = + groupedRecommendations[tokenAddress]; + + // Initialize variables to compute averages + let totalTrustScore = 0; + let totalRiskScore = 0; + let totalConsistencyScore = 0; + const recommenderData = []; + + tokenRecommendations.forEach((recommendation) => { + const tokenPerformance = + this.trustScoreDb.getTokenPerformance( + recommendation.tokenAddress + ); + const recommenderMetrics = + this.trustScoreDb.getRecommenderMetrics( + recommendation.recommenderId + ); + + const trustScore = this.calculateTrustScore( + tokenPerformance, + recommenderMetrics + ); + const consistencyScore = this.calculateConsistencyScore( + tokenPerformance, + recommenderMetrics + ); + const riskScore = this.calculateRiskScore(tokenPerformance); + + // Accumulate scores for averaging + totalTrustScore += trustScore; + totalRiskScore += riskScore; + totalConsistencyScore += consistencyScore; + + recommenderData.push({ + recommenderId: recommendation.recommenderId, + trustScore, + riskScore, + consistencyScore, + recommenderMetrics, + }); + }); + + // Calculate averages for this token + const averageTrustScore = + totalTrustScore / tokenRecommendations.length; + const averageRiskScore = + totalRiskScore / tokenRecommendations.length; + const averageConsistencyScore = + totalConsistencyScore / tokenRecommendations.length; + + return { + tokenAddress, + averageTrustScore, + averageRiskScore, + averageConsistencyScore, + recommenders: recommenderData, + }; + } + ); + + // Sort recommendations by the highest average trust score + result.sort((a, b) => b.averageTrustScore - a.averageTrustScore); + + return result; + } +} + +export const trustScoreProvider: Provider = { + async get( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise { + try { + const trustScoreDb = new TrustScoreDatabase( + runtime.databaseAdapter.db + ); + + // Get the user ID from the message + const userId = message.userId; + + if (!userId) { + console.error("User ID is missing from the message"); + return ""; + } + + // Get the recommender metrics for the user + const recommenderMetrics = + await trustScoreDb.getRecommenderMetrics(userId); + + if (!recommenderMetrics) { + console.error("No recommender metrics found for user:", userId); + return ""; + } + + // Compute the trust score + const trustScore = recommenderMetrics.trustScore; + + const user = await runtime.databaseAdapter.getAccountById(userId); + + // Format the trust score string + const trustScoreString = `${user.name}'s trust score: ${trustScore.toFixed(2)}`; + + return trustScoreString; + } catch (error) { + console.error("Error in trust score provider:", error.message); + return `Failed to fetch trust score: ${error instanceof Error ? error.message : "Unknown error"}`; + } + }, +}; diff --git a/packages/plugin-solana-agentkit/src/providers/wallet.ts b/packages/plugin-solana-agentkit/src/providers/wallet.ts new file mode 100644 index 0000000000..7e3c55580b --- /dev/null +++ b/packages/plugin-solana-agentkit/src/providers/wallet.ts @@ -0,0 +1,391 @@ +import { IAgentRuntime, Memory, Provider, State } from "@elizaos/core"; +import { Connection, PublicKey } from "@solana/web3.js"; +import BigNumber from "bignumber.js"; +import NodeCache from "node-cache"; +import { getWalletKey } from "../keypairUtils"; + +// Provider configuration +const PROVIDER_CONFIG = { + BIRDEYE_API: "https://public-api.birdeye.so", + MAX_RETRIES: 3, + RETRY_DELAY: 2000, + DEFAULT_RPC: "https://api.mainnet-beta.solana.com", + GRAPHQL_ENDPOINT: "https://graph.codex.io/graphql", + TOKEN_ADDRESSES: { + SOL: "So11111111111111111111111111111111111111112", + BTC: "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh", + ETH: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + }, +}; + +export interface Item { + name: string; + address: string; + symbol: string; + decimals: number; + balance: string; + uiAmount: string; + priceUsd: string; + valueUsd: string; + valueSol?: string; +} + +interface WalletPortfolio { + totalUsd: string; + totalSol?: string; + items: Array; +} + +interface _BirdEyePriceData { + data: { + [key: string]: { + price: number; + priceChange24h: number; + }; + }; +} + +interface Prices { + solana: { usd: string }; + bitcoin: { usd: string }; + ethereum: { usd: string }; +} + +export class WalletProvider { + private cache: NodeCache; + + constructor( + private connection: Connection, + private walletPublicKey: PublicKey + ) { + this.cache = new NodeCache({ stdTTL: 300 }); // Cache TTL set to 5 minutes + } + + private async fetchWithRetry( + runtime, + url: string, + options: RequestInit = {} + ): Promise { + let lastError: Error; + + for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) { + try { + const response = await fetch(url, { + ...options, + headers: { + Accept: "application/json", + "x-chain": "solana", + "X-API-KEY": + runtime.getSetting("BIRDEYE_API_KEY", "") || "", + ...options.headers, + }, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error( + `HTTP error! status: ${response.status}, message: ${errorText}` + ); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error(`Attempt ${i + 1} failed:`, error); + lastError = error; + if (i < PROVIDER_CONFIG.MAX_RETRIES - 1) { + const delay = PROVIDER_CONFIG.RETRY_DELAY * Math.pow(2, i); + await new Promise((resolve) => setTimeout(resolve, delay)); + continue; + } + } + } + + console.error( + "All attempts failed. Throwing the last error:", + lastError + ); + throw lastError; + } + + async fetchPortfolioValue(runtime): Promise { + try { + const cacheKey = `portfolio-${this.walletPublicKey.toBase58()}`; + const cachedValue = this.cache.get(cacheKey); + + if (cachedValue) { + console.log("Cache hit for fetchPortfolioValue"); + return cachedValue; + } + console.log("Cache miss for fetchPortfolioValue"); + + const walletData = await this.fetchWithRetry( + runtime, + `${PROVIDER_CONFIG.BIRDEYE_API}/v1/wallet/token_list?wallet=${this.walletPublicKey.toBase58()}` + ); + + if (!walletData?.success || !walletData?.data) { + console.error("No portfolio data available", walletData); + throw new Error("No portfolio data available"); + } + + const data = walletData.data; + const totalUsd = new BigNumber(data.totalUsd.toString()); + const prices = await this.fetchPrices(runtime); + const solPriceInUSD = new BigNumber(prices.solana.usd.toString()); + + const items = data.items.map((item: any) => ({ + ...item, + valueSol: new BigNumber(item.valueUsd || 0) + .div(solPriceInUSD) + .toFixed(6), + name: item.name || "Unknown", + symbol: item.symbol || "Unknown", + priceUsd: item.priceUsd || "0", + valueUsd: item.valueUsd || "0", + })); + + const totalSol = totalUsd.div(solPriceInUSD); + const portfolio = { + totalUsd: totalUsd.toString(), + totalSol: totalSol.toFixed(6), + items: items.sort((a, b) => + new BigNumber(b.valueUsd) + .minus(new BigNumber(a.valueUsd)) + .toNumber() + ), + }; + this.cache.set(cacheKey, portfolio); + return portfolio; + } catch (error) { + console.error("Error fetching portfolio:", error); + throw error; + } + } + + async fetchPortfolioValueCodex(runtime): Promise { + try { + const cacheKey = `portfolio-${this.walletPublicKey.toBase58()}`; + const cachedValue = await this.cache.get(cacheKey); + + if (cachedValue) { + console.log("Cache hit for fetchPortfolioValue"); + return cachedValue; + } + console.log("Cache miss for fetchPortfolioValue"); + + const query = ` + query Balances($walletId: String!, $cursor: String) { + balances(input: { walletId: $walletId, cursor: $cursor }) { + cursor + items { + walletId + tokenId + balance + shiftedBalance + } + } + } + `; + + const variables = { + walletId: `${this.walletPublicKey.toBase58()}:${1399811149}`, + cursor: null, + }; + + const response = await fetch(PROVIDER_CONFIG.GRAPHQL_ENDPOINT, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: + runtime.getSetting("CODEX_API_KEY", "") || "", + }, + body: JSON.stringify({ + query, + variables, + }), + }).then((res) => res.json()); + + const data = response.data?.data?.balances?.items; + + if (!data || data.length === 0) { + console.error("No portfolio data available", data); + throw new Error("No portfolio data available"); + } + + // Fetch token prices + const prices = await this.fetchPrices(runtime); + const solPriceInUSD = new BigNumber(prices.solana.usd.toString()); + + // Reformat items + const items: Item[] = data.map((item: any) => { + return { + name: "Unknown", + address: item.tokenId.split(":")[0], + symbol: item.tokenId.split(":")[0], + decimals: 6, + balance: item.balance, + uiAmount: item.shiftedBalance.toString(), + priceUsd: "", + valueUsd: "", + valueSol: "", + }; + }); + + // Calculate total portfolio value + const totalUsd = items.reduce( + (sum, item) => sum.plus(new BigNumber(item.valueUsd)), + new BigNumber(0) + ); + + const totalSol = totalUsd.div(solPriceInUSD); + + const portfolio: WalletPortfolio = { + totalUsd: totalUsd.toFixed(6), + totalSol: totalSol.toFixed(6), + items: items.sort((a, b) => + new BigNumber(b.valueUsd) + .minus(new BigNumber(a.valueUsd)) + .toNumber() + ), + }; + + // Cache the portfolio for future requests + await this.cache.set(cacheKey, portfolio, 60 * 1000); // Cache for 1 minute + + return portfolio; + } catch (error) { + console.error("Error fetching portfolio:", error); + throw error; + } + } + + async fetchPrices(runtime): Promise { + try { + const cacheKey = "prices"; + const cachedValue = this.cache.get(cacheKey); + + if (cachedValue) { + console.log("Cache hit for fetchPrices"); + return cachedValue; + } + console.log("Cache miss for fetchPrices"); + + const { SOL, BTC, ETH } = PROVIDER_CONFIG.TOKEN_ADDRESSES; + const tokens = [SOL, BTC, ETH]; + const prices: Prices = { + solana: { usd: "0" }, + bitcoin: { usd: "0" }, + ethereum: { usd: "0" }, + }; + + for (const token of tokens) { + const response = await this.fetchWithRetry( + runtime, + `${PROVIDER_CONFIG.BIRDEYE_API}/defi/price?address=${token}`, + { + headers: { + "x-chain": "solana", + }, + } + ); + + if (response?.data?.value) { + const price = response.data.value.toString(); + prices[ + token === SOL + ? "solana" + : token === BTC + ? "bitcoin" + : "ethereum" + ].usd = price; + } else { + console.warn(`No price data available for token: ${token}`); + } + } + + this.cache.set(cacheKey, prices); + return prices; + } catch (error) { + console.error("Error fetching prices:", error); + throw error; + } + } + + formatPortfolio( + runtime, + portfolio: WalletPortfolio, + prices: Prices + ): string { + let output = `${runtime.character.description}\n`; + output += `Wallet Address: ${this.walletPublicKey.toBase58()}\n\n`; + + const totalUsdFormatted = new BigNumber(portfolio.totalUsd).toFixed(2); + const totalSolFormatted = portfolio.totalSol; + + output += `Total Value: $${totalUsdFormatted} (${totalSolFormatted} SOL)\n\n`; + output += "Token Balances:\n"; + + const nonZeroItems = portfolio.items.filter((item) => + new BigNumber(item.uiAmount).isGreaterThan(0) + ); + + if (nonZeroItems.length === 0) { + output += "No tokens found with non-zero balance\n"; + } else { + for (const item of nonZeroItems) { + const valueUsd = new BigNumber(item.valueUsd).toFixed(2); + output += `${item.name} (${item.symbol}): ${new BigNumber( + item.uiAmount + ).toFixed(6)} ($${valueUsd} | ${item.valueSol} SOL)\n`; + } + } + + output += "\nMarket Prices:\n"; + output += `SOL: $${new BigNumber(prices.solana.usd).toFixed(2)}\n`; + output += `BTC: $${new BigNumber(prices.bitcoin.usd).toFixed(2)}\n`; + output += `ETH: $${new BigNumber(prices.ethereum.usd).toFixed(2)}\n`; + + return output; + } + + async getFormattedPortfolio(runtime): Promise { + try { + const [portfolio, prices] = await Promise.all([ + this.fetchPortfolioValue(runtime), + this.fetchPrices(runtime), + ]); + + return this.formatPortfolio(runtime, portfolio, prices); + } catch (error) { + console.error("Error generating portfolio report:", error); + return "Unable to fetch wallet information. Please try again later."; + } + } +} + +const walletProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + _message: Memory, + _state?: State + ): Promise => { + try { + const { publicKey } = await getWalletKey(runtime, false); + + const connection = new Connection( + runtime.getSetting("RPC_URL") || PROVIDER_CONFIG.DEFAULT_RPC + ); + + const provider = new WalletProvider(connection, publicKey); + + return await provider.getFormattedPortfolio(runtime); + } catch (error) { + console.error("Error in wallet provider:", error); + return null; + } + }, +}; + +// Module exports +export { walletProvider }; diff --git a/packages/plugin-solana-agentkit/src/tests/token.test.ts b/packages/plugin-solana-agentkit/src/tests/token.test.ts new file mode 100644 index 0000000000..6b799c1c23 --- /dev/null +++ b/packages/plugin-solana-agentkit/src/tests/token.test.ts @@ -0,0 +1,134 @@ +import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"; +import { TokenProvider } from "../providers/token.ts"; + +// Mock NodeCache +vi.mock("node-cache", () => { + return { + default: vi.fn().mockImplementation(() => ({ + set: vi.fn(), + get: vi.fn().mockReturnValue(null), + })), + }; +}); + +// Mock path module +vi.mock("path", async () => { + const actual = await vi.importActual("path"); + return { + ...(actual as any), + join: vi.fn().mockImplementation((...args) => args.join("/")), + }; +}); + +// Mock the WalletProvider +const mockWalletProvider = { + fetchPortfolioValue: vi.fn(), +}; + +// Mock the ICacheManager +const mockCacheManager = { + get: vi.fn().mockResolvedValue(null), + set: vi.fn(), +}; + +// Mock fetch globally +const mockFetch = vi.fn(); +global.fetch = mockFetch; + +describe("TokenProvider", () => { + let tokenProvider: TokenProvider; + const TEST_TOKEN_ADDRESS = "2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh"; + + beforeEach(() => { + vi.clearAllMocks(); + mockCacheManager.get.mockResolvedValue(null); + + // Create new instance of TokenProvider with mocked dependencies + tokenProvider = new TokenProvider( + TEST_TOKEN_ADDRESS, + mockWalletProvider as any, + mockCacheManager as any + ); + }); + + afterEach(() => { + vi.clearAllTimers(); + }); + + describe("Cache Management", () => { + it("should use cached data when available", async () => { + const mockData = { test: "data" }; + mockCacheManager.get.mockResolvedValueOnce(mockData); + + const result = await (tokenProvider as any).getCachedData( + "test-key" + ); + + expect(result).toEqual(mockData); + expect(mockCacheManager.get).toHaveBeenCalledTimes(1); + }); + + it("should write data to both caches", async () => { + const testData = { test: "data" }; + + await (tokenProvider as any).setCachedData("test-key", testData); + + expect(mockCacheManager.set).toHaveBeenCalledWith( + expect.stringContaining("test-key"), + testData, + expect.any(Object) + ); + }); + }); + + describe("Wallet Integration", () => { + it("should fetch tokens in wallet", async () => { + const mockItems = [ + { symbol: "SOL", address: "address1" }, + { symbol: "BTC", address: "address2" }, + ]; + + mockWalletProvider.fetchPortfolioValue.mockResolvedValueOnce({ + items: mockItems, + }); + + const result = await tokenProvider.getTokensInWallet({} as any); + + expect(result).toEqual(mockItems); + expect( + mockWalletProvider.fetchPortfolioValue + ).toHaveBeenCalledTimes(1); + }); + + it("should find token in wallet by symbol", async () => { + const mockItems = [ + { symbol: "SOL", address: "address1" }, + { symbol: "BTC", address: "address2" }, + ]; + + mockWalletProvider.fetchPortfolioValue.mockResolvedValueOnce({ + items: mockItems, + }); + + const result = await tokenProvider.getTokenFromWallet( + {} as any, + "SOL" + ); + + expect(result).toBe("address1"); + }); + + it("should return null for token not in wallet", async () => { + mockWalletProvider.fetchPortfolioValue.mockResolvedValueOnce({ + items: [], + }); + + const result = await tokenProvider.getTokenFromWallet( + {} as any, + "NONEXISTENT" + ); + + expect(result).toBeNull(); + }); + }); +}); diff --git a/packages/plugin-solana-agentkit/src/types/token.ts b/packages/plugin-solana-agentkit/src/types/token.ts new file mode 100644 index 0000000000..1fca4c37c3 --- /dev/null +++ b/packages/plugin-solana-agentkit/src/types/token.ts @@ -0,0 +1,302 @@ +export interface TokenSecurityData { + ownerBalance: string; + creatorBalance: string; + ownerPercentage: number; + creatorPercentage: number; + top10HolderBalance: string; + top10HolderPercent: number; +} + +export interface TokenCodex { + id: string; + address: string; + cmcId: number; + decimals: number; + name: string; + symbol: string; + totalSupply: string; + circulatingSupply: string; + imageThumbUrl: string; + blueCheckmark: boolean; + isScam: boolean; +} + +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; + unique_wallet_1h: number; + unique_wallet_history_1h: number; + unique_wallet_1h_change_percent: number; + unique_wallet_2h: number; + unique_wallet_history_2h: number; + unique_wallet_2h_change_percent: number; + unique_wallet_4h: number; + unique_wallet_history_4h: number; + unique_wallet_4h_change_percent: number; + unique_wallet_8h: number; + unique_wallet_history_8h: number | null; + unique_wallet_8h_change_percent: number | null; + unique_wallet_24h: number; + unique_wallet_history_24h: number | null; + 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 | null; + trade_8h_change_percent: number | null; + sell_8h: number; + sell_history_8h: number | null; + sell_8h_change_percent: number | null; + buy_8h: number; + buy_history_8h: number | null; + buy_8h_change_percent: number | null; + volume_8h: number; + volume_8h_usd: number; + volume_history_8h: number; + volume_history_8h_usd: number; + volume_8h_change_percent: number | null; + 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 | null; + 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 | null; + trade_24h: number; + trade_history_24h: number; + trade_24h_change_percent: number | null; + sell_24h: number; + sell_history_24h: number; + sell_24h_change_percent: number | null; + buy_24h: number; + buy_history_24h: number; + buy_24h_change_percent: number | null; + volume_24h: number; + volume_24h_usd: number; + volume_history_24h: number; + volume_history_24h_usd: number; + volume_24h_change_percent: number | null; + 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 | null; + 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 | null; +} + +export interface HolderData { + address: string; + balance: string; +} + +export interface ProcessedTokenData { + security: TokenSecurityData; + tradeData: TokenTradeData; + holderDistributionTrend: string; // 'increasing' | 'decreasing' | 'stable' + highValueHolders: Array<{ + holderAddress: string; + balanceUsd: string; + }>; + recentTrades: boolean; + highSupplyHoldersCount: number; + dexScreenerData: DexScreenerData; + + isDexScreenerListed: boolean; + isDexScreenerPaid: boolean; + tokenCodex: TokenCodex; +} + +export interface DexScreenerPair { + chainId: string; + dexId: string; + url: string; + pairAddress: string; + baseToken: { + address: string; + name: string; + symbol: string; + }; + quoteToken: { + address: string; + name: string; + symbol: string; + }; + priceNative: string; + priceUsd: string; + txns: { + m5: { buys: number; sells: number }; + h1: { buys: number; sells: number }; + h6: { buys: number; sells: number }; + h24: { buys: number; sells: number }; + }; + volume: { + h24: number; + h6: number; + h1: number; + m5: number; + }; + priceChange: { + m5: number; + h1: number; + h6: number; + h24: number; + }; + liquidity: { + usd: number; + base: number; + quote: number; + }; + fdv: number; + marketCap: number; + pairCreatedAt: number; + info: { + imageUrl: string; + websites: { label: string; url: string }[]; + socials: { type: string; url: string }[]; + }; + boosts: { + active: number; + }; +} + +export interface DexScreenerData { + schemaVersion: string; + pairs: DexScreenerPair[]; +} + +export interface Prices { + solana: { usd: string }; + bitcoin: { usd: string }; + ethereum: { usd: string }; +} + +export interface CalculatedBuyAmounts { + none: 0; + low: number; + medium: number; + high: number; +} diff --git a/packages/plugin-solana-agentkit/tsconfig.json b/packages/plugin-solana-agentkit/tsconfig.json new file mode 100644 index 0000000000..73993deaaf --- /dev/null +++ b/packages/plugin-solana-agentkit/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-solana-agentkit/tsup.config.ts b/packages/plugin-solana-agentkit/tsup.config.ts new file mode 100644 index 0000000000..dd25475bb6 --- /dev/null +++ b/packages/plugin-solana-agentkit/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 + ], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3e979dca71..4a35365e5a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -186,6 +186,9 @@ importers: '@elizaos/plugin-solana': specifier: workspace:* version: link:../packages/plugin-solana + '@elizaos/plugin-solana-agentkit': + specifier: workspace:* + version: link:../packages/plugin-solana-agentkit '@elizaos/plugin-starknet': specifier: workspace:* version: link:../packages/plugin-starknet @@ -796,7 +799,7 @@ importers: version: 1.0.15 langchain: specifier: 0.3.6 - version: 0.3.6(@langchain/core@0.3.26(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(axios@1.7.9)(encoding@0.1.13)(handlebars@4.7.8)(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)) + version: 0.3.6(@langchain/core@0.3.26(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(@langchain/groq@0.1.2(@langchain/core@0.3.26(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13))(axios@1.7.9)(encoding@0.1.13)(handlebars@4.7.8)(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)) ollama-ai-provider: specifier: 0.16.1 version: 0.16.1(zod@3.23.8) @@ -1545,6 +1548,60 @@ importers: specifier: 7.1.0 version: 7.1.0 + packages/plugin-solana-agentkit: + 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 + '@elizaos/plugin-tee': + specifier: workspace:* + version: link:../plugin-tee + '@elizaos/plugin-trustdb': + specifier: workspace:* + version: link:../plugin-trustdb + '@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) + solana-agent-kit: + specifier: ^1.2.0 + version: 1.2.0(@noble/hashes@1.6.1)(axios@1.7.9)(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(handlebars@4.7.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + 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-starknet: dependencies: '@avnu/avnu-sdk': @@ -2987,6 +3044,16 @@ packages: bs58: ^6.0.0 viem: ^2.21.0 + '@bonfida/sns-records@0.0.1': + resolution: {integrity: sha512-i28w9+BMFufhhpmLQCNx1CKKXTsEn+5RT18VFpPqdGO3sqaYlnUWC1m3wDpOvlzGk498dljgRpRo5wmcsnuEMg==} + peerDependencies: + '@solana/web3.js': ^1.87.3 + + '@bonfida/spl-name-service@3.0.7': + resolution: {integrity: sha512-okOLXhy+fQoyQ/sZgMleO5RrIZfTkWEoHMxWgUqg6RP/MTBlrKxlhKC6ymKn4UUe0C5s3Nb8A+3Ams7vX0nMDg==} + peerDependencies: + '@solana/web3.js': ^1.87.3 + '@braintree/sanitize-url@7.1.0': resolution: {integrity: sha512-o+UlMLt49RvtCASlOMW0AkHnabN9wR9rwCCherxO0yG4Npy34GkvrAqdXQvrhNs+jh+gkK8gB8Lf05qL/O7KWg==} @@ -4714,6 +4781,27 @@ packages: resolution: {integrity: sha512-6RUQHEp8wv+JwtYIIEBYBzbLlcAQZFc7EDOgAM0ukExjh9HiXoJzoWpgMRRCrr/koIbtwXPJUqBprZK1I1CXHQ==} engines: {node: '>=18'} + '@langchain/groq@0.1.2': + resolution: {integrity: sha512-bgQ9yGoNHOwG6LG2ngGvSNxF/1U1c1u3vKmFWmzecFIcBoQQOJY0jb0MrL3g1uTife0Sr3zxkWKXQg2aK/U4Sg==} + engines: {node: '>=18'} + peerDependencies: + '@langchain/core': '>=0.2.21 <0.4.0' + + '@langchain/langgraph-checkpoint@0.0.13': + resolution: {integrity: sha512-amdmBcNT8a9xP2VwcEWxqArng4gtRDcnVyVI4DsQIo1Aaz8e8+hH17zSwrUF3pt1pIYztngIfYnBOim31mtKMg==} + engines: {node: '>=18'} + peerDependencies: + '@langchain/core': '>=0.2.31 <0.4.0' + + '@langchain/langgraph-sdk@0.0.32': + resolution: {integrity: sha512-KQyM9kLO7T6AxwNrceajH7JOybP3pYpvUPnhiI2rrVndI1WyZUJ1eVC1e722BVRAPi6o+WcoTT4uMSZVinPOtA==} + + '@langchain/langgraph@0.2.36': + resolution: {integrity: sha512-zxk7ZCVxP0/Ut9785EiXCS7BE7sXd8cu943mcZUF2aNFUaQRTBbbiKpNdR3nb1+xO/B+HVktrJT2VFdkAywnng==} + engines: {node: '>=18'} + peerDependencies: + '@langchain/core': '>=0.2.36 <0.3.0 || >=0.3.9 < 0.4.0' + '@langchain/openai@0.3.16': resolution: {integrity: sha512-Om9HRlTeI0Ou6D4pfxbWHop4WGfkCdV/7v1W/+Jr7NSf0BNoA9jk5GqGms8ZtOYSGgPvizDu3i0TrM3B4cN4NA==} engines: {node: '>=18'} @@ -4806,6 +4894,14 @@ packages: '@lifi/types@16.3.0': resolution: {integrity: sha512-rYMdXRdNOyJb5tI5CXfqxU4k62GiJrElx0DEZ8ZRFYFtljg69X6hrMKER1wVWkRpcB67Ca8SKebLnufy7qCaTw==} + '@lightprotocol/compressed-token@0.17.1': + resolution: {integrity: sha512-493KCmZGw1BcHVRJaeRm8EEs+L7gX8dwY7JG13w2pfgOMtZXZ7Wxt261jFJxQJzRLTrUSlrbRJOmfW1+S1Y8SQ==} + peerDependencies: + '@lightprotocol/stateless.js': 0.17.1 + + '@lightprotocol/stateless.js@0.17.1': + resolution: {integrity: sha512-EjId1n33A6dBwpce33Wsa/fs/CDKtMtRrkxbApH0alXrnEXmbW6QhIViXOrKYXjZ4uJQM1xsBtsKe0vqJ4nbtQ==} + '@lit-labs/ssr-dom-shim@1.2.1': resolution: {integrity: sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ==} @@ -4878,6 +4974,12 @@ packages: resolution: {integrity: sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==} engines: {node: '>=12.0.0'} + '@metaplex-foundation/mpl-core@1.1.1': + resolution: {integrity: sha512-h1kLw+cGaV8SiykoHDb1/G01+VYqtJXAt0uGuO5+2Towsdtc6ET4M62iqUnh4EacTVMIW1yYHsKsG/LYWBCKaA==} + peerDependencies: + '@metaplex-foundation/umi': '>=0.8.2 < 1' + '@noble/hashes': ^1.3.1 + '@metaplex-foundation/mpl-token-metadata@3.3.0': resolution: {integrity: sha512-t5vO8Wr3ZZZPGrVrGNcosX5FMkwQSgBiVMQMRNDG2De7voYFJmIibD5jdG05EoQ4Y5kZVEiwhYaO+wJB3aO5AA==} peerDependencies: @@ -5748,6 +5850,22 @@ packages: resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} + '@orca-so/common-sdk@0.6.4': + resolution: {integrity: sha512-iOiC6exTA9t2CEOaUPoWlNP3soN/1yZFjoz1mSf7NvOqo/PJZeIdWpB7BRXwU0mGGatjxU4SFgMGQ8NrSx+ONw==} + peerDependencies: + '@solana/spl-token': ^0.4.1 + '@solana/web3.js': ^1.90.0 + decimal.js: ^10.4.3 + + '@orca-so/whirlpools-sdk@0.13.12': + resolution: {integrity: sha512-+LOqGTe0DYUsYwemltOU4WQIviqoICQlIcAmmEX/WnBh6wntpcLDcXkPV6dBHW7NA2/J8WEVAZ50biLJb4subg==} + peerDependencies: + '@coral-xyz/anchor': ~0.29.0 + '@orca-so/common-sdk': 0.6.4 + '@solana/spl-token': ^0.4.8 + '@solana/web3.js': ^1.90.0 + decimal.js: ^10.4.3 + '@parcel/watcher-android-arm64@2.5.0': resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==} engines: {node: '>= 10.0.0'} @@ -6188,6 +6306,9 @@ packages: '@raydium-io/raydium-sdk-v2@0.1.82-alpha': resolution: {integrity: sha512-PScLnWZV5Y/igcvP4hbD/1ztzW2w5a2YStolu9A5VT6uB2q+izeo+SE7IqzZggyaReXyisjdkNGpB/kMdkdJGQ==} + '@raydium-io/raydium-sdk-v2@0.1.95-alpha': + resolution: {integrity: sha512-+u7yxo/R1JDysTCzOuAlh90ioBe2DlM2Hbcz/tFsxP/YzmnYQzShvNjcmc0361a4zJhmlrEJfpFXW0J3kkX5vA==} + '@react-icons/all-files@4.1.0': resolution: {integrity: sha512-hxBI2UOuVaI3O/BhQfhtb4kcGn9ft12RWAFVMUeNjqqhLsHvFtzIkFaptBJpFDANTKoDfdVoHTKZDlwKCACbMQ==} peerDependencies: @@ -6855,6 +6976,11 @@ packages: '@solana/codecs-core@2.0.0-preview.2': resolution: {integrity: sha512-gLhCJXieSCrAU7acUJjbXl+IbGnqovvxQLlimztPoGgfLQ1wFYu+XJswrEVQqknZYK1pgxpxH3rZ+OKFs0ndQg==} + '@solana/codecs-core@2.0.0-preview.4': + resolution: {integrity: sha512-A0VVuDDA5kNKZUinOqHxJQK32aKTucaVbvn31YenGzHX1gPqq+SOnFwgaEY6pq4XEopSmaK16w938ZQS8IvCnw==} + peerDependencies: + typescript: '>=5' + '@solana/codecs-core@2.0.0-rc.1': resolution: {integrity: sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ==} peerDependencies: @@ -6863,6 +6989,11 @@ packages: '@solana/codecs-data-structures@2.0.0-preview.2': resolution: {integrity: sha512-Xf5vIfromOZo94Q8HbR04TbgTwzigqrKII0GjYr21K7rb3nba4hUW2ir8kguY7HWFBcjHGlU5x3MevKBOLp3Zg==} + '@solana/codecs-data-structures@2.0.0-preview.4': + resolution: {integrity: sha512-nt2k2eTeyzlI/ccutPcG36M/J8NAYfxBPI9h/nQjgJ+M+IgOKi31JV8StDDlG/1XvY0zyqugV3I0r3KAbZRJpA==} + peerDependencies: + typescript: '>=5' + '@solana/codecs-data-structures@2.0.0-rc.1': resolution: {integrity: sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog==} peerDependencies: @@ -6871,6 +7002,11 @@ packages: '@solana/codecs-numbers@2.0.0-preview.2': resolution: {integrity: sha512-aLZnDTf43z4qOnpTcDsUVy1Ci9im1Md8thWipSWbE+WM9ojZAx528oAql+Cv8M8N+6ALKwgVRhPZkto6E59ARw==} + '@solana/codecs-numbers@2.0.0-preview.4': + resolution: {integrity: sha512-Q061rLtMadsO7uxpguT+Z7G4UHnjQ6moVIxAQxR58nLxDPCC7MB1Pk106/Z7NDhDLHTcd18uO6DZ7ajHZEn2XQ==} + peerDependencies: + typescript: '>=5' + '@solana/codecs-numbers@2.0.0-rc.1': resolution: {integrity: sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ==} peerDependencies: @@ -6881,6 +7017,12 @@ packages: peerDependencies: fastestsmallesttextencoderdecoder: ^1.0.22 + '@solana/codecs-strings@2.0.0-preview.4': + resolution: {integrity: sha512-YDbsQePRWm+xnrfS64losSGRg8Wb76cjK1K6qfR8LPmdwIC3787x9uW5/E4icl/k+9nwgbIRXZ65lpF+ucZUnw==} + peerDependencies: + fastestsmallesttextencoderdecoder: ^1.0.22 + typescript: '>=5' + '@solana/codecs-strings@2.0.0-rc.1': resolution: {integrity: sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g==} peerDependencies: @@ -6890,6 +7032,11 @@ packages: '@solana/codecs@2.0.0-preview.2': resolution: {integrity: sha512-4HHzCD5+pOSmSB71X6w9ptweV48Zj1Vqhe732+pcAQ2cMNnN0gMPMdDq7j3YwaZDZ7yrILVV/3+HTnfT77t2yA==} + '@solana/codecs@2.0.0-preview.4': + resolution: {integrity: sha512-gLMupqI4i+G4uPi2SGF/Tc1aXcviZF2ybC81x7Q/fARamNSgNOCUUoSCg9nWu1Gid6+UhA7LH80sWI8XjKaRog==} + peerDependencies: + typescript: '>=5' + '@solana/codecs@2.0.0-rc.1': resolution: {integrity: sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ==} peerDependencies: @@ -6899,6 +7046,12 @@ packages: resolution: {integrity: sha512-H2DZ1l3iYF5Rp5pPbJpmmtCauWeQXRJapkDg8epQ8BJ7cA2Ut/QEtC3CMmw/iMTcuS6uemFNLcWvlOfoQhvQuA==} hasBin: true + '@solana/errors@2.0.0-preview.4': + resolution: {integrity: sha512-kadtlbRv2LCWr8A9V22On15Us7Nn8BvqNaOB4hXsTB3O0fU40D1ru2l+cReqLcRPij4znqlRzW9Xi0m6J5DIhA==} + hasBin: true + peerDependencies: + typescript: '>=5' + '@solana/errors@2.0.0-rc.1': resolution: {integrity: sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ==} hasBin: true @@ -6908,6 +7061,11 @@ packages: '@solana/options@2.0.0-preview.2': resolution: {integrity: sha512-FAHqEeH0cVsUOTzjl5OfUBw2cyT8d5Oekx4xcn5hn+NyPAfQJgM3CEThzgRD6Q/4mM5pVUnND3oK/Mt1RzSE/w==} + '@solana/options@2.0.0-preview.4': + resolution: {integrity: sha512-tv2O/Frxql/wSe3jbzi5nVicIWIus/BftH+5ZR+r9r3FO0/htEllZS5Q9XdbmSboHu+St87584JXeDx3xm4jaA==} + peerDependencies: + typescript: '>=5' + '@solana/options@2.0.0-rc.1': resolution: {integrity: sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA==} peerDependencies: @@ -6919,6 +7077,12 @@ packages: peerDependencies: '@solana/web3.js': ^1.91.6 + '@solana/spl-token-group@0.0.5': + resolution: {integrity: sha512-CLJnWEcdoUBpQJfx9WEbX3h6nTdNiUzswfFdkABUik7HVwSNA98u5AYvBVK2H93d9PGMOHAak2lHW9xr+zAJGQ==} + engines: {node: '>=16'} + peerDependencies: + '@solana/web3.js': ^1.94.0 + '@solana/spl-token-group@0.0.7': resolution: {integrity: sha512-V1N/iX7Cr7H0uazWUT2uk27TMqlqedpXHRqqAbVO2gvmJyT0E0ummMEAVQeXZ05ZhQ/xF39DLSdBp90XebWEug==} engines: {node: '>=16'} @@ -6937,6 +7101,12 @@ packages: peerDependencies: '@solana/web3.js': ^1.91.6 + '@solana/spl-token@0.4.8': + resolution: {integrity: sha512-RO0JD9vPRi4LsAbMUdNbDJ5/cv2z11MGhtAvFeRzT4+hAGE/FUzRi0tkkWtuCfSIU3twC6CtmAihRp/+XXjWsA==} + engines: {node: '>=16'} + peerDependencies: + '@solana/web3.js': ^1.94.0 + '@solana/spl-token@0.4.9': resolution: {integrity: sha512-g3wbj4F4gq82YQlwqhPB0gHFXfgsC6UmyGMxtSLf/BozT/oKd59465DbnlUK8L8EcimKMavxsVAMoLcEdeCicg==} engines: {node: '>=16'} @@ -6957,6 +7127,9 @@ packages: resolution: {integrity: sha512-tUd9srDLkRpe1BYg7we+c4UhRQkq+XQWswsr/L1xfGmoRDF47BPSXf4zE7ZU2GRBGvxtGt7lwJVAufQyQYhxTQ==} engines: {node: '>=16'} + '@solana/web3.js@1.95.3': + resolution: {integrity: sha512-O6rPUN0w2fkNqx/Z3QJMB9L225Ex10PRDH8bTaIUPZXMPV0QP8ZpPvjQnXK+upUczlRgzHzd6SjKIha1p+I6og==} + '@solana/web3.js@1.95.5': resolution: {integrity: sha512-hU9cBrbg1z6gEjLH9vwIckGBVB78Ijm0iZFNk4ocm5OD82piPwuk3MeQ1rfiKD9YQtr95krrcaopb49EmQJlRg==} @@ -8716,6 +8889,9 @@ packages: base-x@3.0.10: resolution: {integrity: sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==} + base-x@4.0.0: + resolution: {integrity: sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==} + base-x@5.0.0: resolution: {integrity: sha512-sMW3VGSX1QWVFA6l8U62MLKz29rRfpTlYdCqLdpLo1/Yd4zZwSbnUaDfciIAowAqvq7YFnWq9hrhdg1KYgc1lQ==} @@ -8868,6 +9044,9 @@ packages: borsh@1.0.0: resolution: {integrity: sha512-fSVWzzemnyfF89EPwlUNsrS5swF5CrtiN4e+h0/lLf4dz2he4L3ndM20PS9wj7ICSkXJe/TQUHdaPTq15b1mNQ==} + borsh@2.0.0: + resolution: {integrity: sha512-kc9+BgR3zz9+cjbwM8ODoUB4fs3X3I5A/HtX7LZKxCLaMrEeDFoBpnhZY//DTS1VZBSs6S5v46RZRbZjRFspEg==} + bottleneck@2.19.5: resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} @@ -8952,6 +9131,9 @@ packages: bs58@4.0.1: resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} + bs58@5.0.0: + resolution: {integrity: sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==} + bs58@6.0.0: resolution: {integrity: sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==} @@ -11702,6 +11884,9 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + graphemesplit@2.4.4: + resolution: {integrity: sha512-lKrpp1mk1NH26USxC/Asw4OHbhSQf5XfrWZ+CDv/dFVvd1j17kFgMotdJvOesmHkbFX9P9sBfpH8VogxOWLg8w==} + graphql-request@6.1.0: resolution: {integrity: sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==} peerDependencies: @@ -11721,6 +11906,9 @@ packages: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} + groq-sdk@0.5.0: + resolution: {integrity: sha512-RVmhW7qZ+XZoy5fIuSdx/LGQJONpL8MHgZEW7dFwTdgkzStub2XQx6OKv28CHogijdwH41J+Npj/z2jBPu3vmw==} + gtoken@7.1.0: resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} engines: {node: '>=14.0.0'} @@ -16939,6 +17127,10 @@ packages: resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + solana-agent-kit@1.2.0: + resolution: {integrity: sha512-JdNXRIKsKsz8nPuCOLdSMQ6sejqwafClJBaQL1eGHU7cpyJZbTLHatD/VFpO2lv+nr6Sqg+G05mtCRyV0ELc0Q==} + engines: {node: '>=23.1.0', pnpm: '>=8.0.0'} + solc@0.8.26: resolution: {integrity: sha512-yiPQNVf5rBFHwN6SIf3TUUvVAFKcQqmSUFeq+fb6pNRCo0ZCgpYOZDi3BVoezCPIAcKrVYd/qXlBLUP9wVrZ9g==} engines: {node: '>=10.0.0'} @@ -17513,6 +17705,9 @@ packages: tiny-emitter@2.1.0: resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -18045,6 +18240,9 @@ packages: resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} engines: {node: '>=4'} + unicode-trie@2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + unicorn-magic@0.1.0: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} @@ -20970,6 +21168,32 @@ snapshots: bs58: 6.0.0 viem: 2.21.53(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + '@bonfida/sns-records@0.0.1(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))': + dependencies: + '@solana/web3.js': 1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + borsh: 1.0.0 + bs58: 5.0.0 + buffer: 6.0.3 + + '@bonfida/spl-name-service@3.0.7(@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: + '@bonfida/sns-records': 0.0.1(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)) + '@noble/curves': 1.7.0 + '@scure/base': 1.2.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.6.3)(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) + borsh: 2.0.0 + buffer: 6.0.3 + graphemesplit: 2.4.4 + ipaddr.js: 2.2.0 + punycode: 2.3.1 + transitivePeerDependencies: + - bufferutil + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + '@braintree/sanitize-url@7.1.0': {} '@cfworker/json-schema@4.0.3': {} @@ -23570,6 +23794,64 @@ snapshots: transitivePeerDependencies: - openai + '@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8))': + dependencies: + '@cfworker/json-schema': 4.0.3 + ansi-styles: 5.2.0 + camelcase: 6.3.0 + decamelize: 1.2.0 + js-tiktoken: 1.0.15 + langsmith: 0.2.13(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)) + mustache: 4.2.0 + p-queue: 6.6.2 + p-retry: 4.6.2 + uuid: 10.0.0 + zod: 3.23.8 + zod-to-json-schema: 3.24.1(zod@3.23.8) + transitivePeerDependencies: + - openai + + '@langchain/groq@0.1.2(@langchain/core@0.3.26(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)': + dependencies: + '@langchain/core': 0.3.26(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)) + '@langchain/openai': 0.3.16(@langchain/core@0.3.26(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) + groq-sdk: 0.5.0(encoding@0.1.13) + zod: 3.23.8 + zod-to-json-schema: 3.24.1(zod@3.23.8) + transitivePeerDependencies: + - encoding + optional: true + + '@langchain/groq@0.1.2(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)': + dependencies: + '@langchain/core': 0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)) + '@langchain/openai': 0.3.16(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) + groq-sdk: 0.5.0(encoding@0.1.13) + zod: 3.23.8 + zod-to-json-schema: 3.24.1(zod@3.23.8) + transitivePeerDependencies: + - encoding + + '@langchain/langgraph-checkpoint@0.0.13(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)))': + dependencies: + '@langchain/core': 0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)) + uuid: 10.0.0 + + '@langchain/langgraph-sdk@0.0.32': + dependencies: + '@types/json-schema': 7.0.15 + p-queue: 6.6.2 + p-retry: 4.6.2 + uuid: 9.0.1 + + '@langchain/langgraph@0.2.36(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)))': + dependencies: + '@langchain/core': 0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)) + '@langchain/langgraph-checkpoint': 0.0.13(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8))) + '@langchain/langgraph-sdk': 0.0.32 + uuid: 10.0.0 + zod: 3.23.8 + '@langchain/openai@0.3.16(@langchain/core@0.3.26(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)': dependencies: '@langchain/core': 0.3.26(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)) @@ -23580,11 +23862,26 @@ snapshots: transitivePeerDependencies: - encoding + '@langchain/openai@0.3.16(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)': + dependencies: + '@langchain/core': 0.3.26(openai@4.77.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) + zod: 3.23.8 + zod-to-json-schema: 3.24.1(zod@3.23.8) + transitivePeerDependencies: + - encoding + '@langchain/textsplitters@0.1.0(@langchain/core@0.3.26(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))': dependencies: '@langchain/core': 0.3.26(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)) js-tiktoken: 1.0.15 + '@langchain/textsplitters@0.1.0(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)))': + dependencies: + '@langchain/core': 0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)) + js-tiktoken: 1.0.15 + '@leichtgewicht/ip-codec@2.0.5': {} '@lens-protocol/blockchain-bindings@0.10.2(@jest/globals@29.7.0)(bufferutil@4.0.8)(utf-8-validate@5.0.10)': @@ -23825,6 +24122,34 @@ snapshots: '@lifi/types@16.3.0': {} + '@lightprotocol/compressed-token@0.17.1(@lightprotocol/stateless.js@0.17.1(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: + '@coral-xyz/anchor': 0.29.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@lightprotocol/stateless.js': 0.17.1(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@solana/spl-token': 0.4.8(@solana/web3.js@1.95.3(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': 1.95.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + buffer: 6.0.3 + tweetnacl: 1.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + + '@lightprotocol/stateless.js@0.17.1(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)': + dependencies: + '@coral-xyz/anchor': 0.29.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@noble/hashes': 1.5.0 + '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + buffer: 6.0.3 + superstruct: 2.0.2 + tweetnacl: 1.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + '@lit-labs/ssr-dom-shim@1.2.1': {} '@lit-protocol/access-control-conditions@2.1.62(bufferutil@4.0.8)(utf-8-validate@5.0.10)': @@ -24064,6 +24389,12 @@ snapshots: tweetnacl: 1.0.3 tweetnacl-util: 0.15.1 + '@metaplex-foundation/mpl-core@1.1.1(@metaplex-foundation/umi@0.9.2)(@noble/hashes@1.6.1)': + dependencies: + '@metaplex-foundation/umi': 0.9.2 + '@msgpack/msgpack': 3.0.0-beta2 + '@noble/hashes': 1.6.1 + '@metaplex-foundation/mpl-token-metadata@3.3.0(@metaplex-foundation/umi@0.9.2)': dependencies: '@metaplex-foundation/mpl-toolbox': 0.9.4(@metaplex-foundation/umi@0.9.2) @@ -24088,6 +24419,21 @@ snapshots: transitivePeerDependencies: - encoding + '@metaplex-foundation/umi-bundle-defaults@0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(encoding@0.1.13)': + dependencies: + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-downloader-http': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)) + '@metaplex-foundation/umi-http-fetch': 0.9.2(@metaplex-foundation/umi@0.9.2)(encoding@0.1.13) + '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-rpc-chunk-get-accounts': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)) + '@metaplex-foundation/umi-serializer-data-view': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-transaction-factory-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(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: + - encoding + '@metaplex-foundation/umi-downloader-http@0.9.2(@metaplex-foundation/umi@0.9.2)': dependencies: '@metaplex-foundation/umi': 0.9.2 @@ -24099,6 +24445,13 @@ snapshots: '@noble/curves': 1.7.0 '@solana/web3.js': 1.95.5(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@metaplex-foundation/umi-eddsa-web3js@0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))': + dependencies: + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)) + '@noble/curves': 1.7.0 + '@solana/web3.js': 1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@metaplex-foundation/umi-http-fetch@0.9.2(@metaplex-foundation/umi@0.9.2)(encoding@0.1.13)': dependencies: '@metaplex-foundation/umi': 0.9.2 @@ -24126,6 +24479,12 @@ snapshots: '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.5(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)) '@solana/web3.js': 1.95.5(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@metaplex-foundation/umi-rpc-web3js@0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))': + dependencies: + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(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) + '@metaplex-foundation/umi-serializer-data-view@0.9.2(@metaplex-foundation/umi@0.9.2)': dependencies: '@metaplex-foundation/umi': 0.9.2 @@ -24154,12 +24513,24 @@ snapshots: '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.5(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)) '@solana/web3.js': 1.95.5(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@metaplex-foundation/umi-transaction-factory-web3js@0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))': + dependencies: + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(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) + '@metaplex-foundation/umi-web3js-adapters@0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.5(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))': dependencies: '@metaplex-foundation/umi': 0.9.2 '@solana/web3.js': 1.95.5(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) buffer: 6.0.3 + '@metaplex-foundation/umi-web3js-adapters@0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))': + dependencies: + '@metaplex-foundation/umi': 0.9.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 + '@metaplex-foundation/umi@0.9.2': dependencies: '@metaplex-foundation/umi-options': 0.8.9 @@ -25338,6 +25709,22 @@ snapshots: '@opentelemetry/api@1.9.0': {} + '@orca-so/common-sdk@0.6.4(@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))(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(decimal.js@10.4.3)': + dependencies: + '@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) + '@solana/web3.js': 1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + decimal.js: 10.4.3 + tiny-invariant: 1.3.3 + + '@orca-so/whirlpools-sdk@0.13.12(@coral-xyz/anchor@0.29.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(@orca-so/common-sdk@0.6.4(@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))(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(decimal.js@10.4.3))(@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))(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(decimal.js@10.4.3)': + dependencies: + '@coral-xyz/anchor': 0.29.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@orca-so/common-sdk': 0.6.4(@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))(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(decimal.js@10.4.3) + '@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) + '@solana/web3.js': 1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + decimal.js: 10.4.3 + tiny-invariant: 1.3.3 + '@parcel/watcher-android-arm64@2.5.0': optional: true @@ -25788,6 +26175,28 @@ snapshots: - typescript - utf-8-validate + '@raydium-io/raydium-sdk-v2@0.1.95-alpha(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 + '@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) + '@solana/web3.js': 1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + axios: 1.7.9(debug@4.4.0) + big.js: 6.2.2 + bn.js: 5.2.1 + dayjs: 1.11.13 + decimal.js-light: 2.5.1 + jsonfile: 6.1.0 + 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 @@ -26662,6 +27071,11 @@ snapshots: dependencies: '@solana/errors': 2.0.0-preview.2 + '@solana/codecs-core@2.0.0-preview.4(typescript@5.6.3)': + dependencies: + '@solana/errors': 2.0.0-preview.4(typescript@5.6.3) + typescript: 5.6.3 + '@solana/codecs-core@2.0.0-rc.1(typescript@5.6.3)': dependencies: '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) @@ -26673,6 +27087,13 @@ snapshots: '@solana/codecs-numbers': 2.0.0-preview.2 '@solana/errors': 2.0.0-preview.2 + '@solana/codecs-data-structures@2.0.0-preview.4(typescript@5.6.3)': + dependencies: + '@solana/codecs-core': 2.0.0-preview.4(typescript@5.6.3) + '@solana/codecs-numbers': 2.0.0-preview.4(typescript@5.6.3) + '@solana/errors': 2.0.0-preview.4(typescript@5.6.3) + typescript: 5.6.3 + '@solana/codecs-data-structures@2.0.0-rc.1(typescript@5.6.3)': dependencies: '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) @@ -26685,6 +27106,12 @@ snapshots: '@solana/codecs-core': 2.0.0-preview.2 '@solana/errors': 2.0.0-preview.2 + '@solana/codecs-numbers@2.0.0-preview.4(typescript@5.6.3)': + dependencies: + '@solana/codecs-core': 2.0.0-preview.4(typescript@5.6.3) + '@solana/errors': 2.0.0-preview.4(typescript@5.6.3) + typescript: 5.6.3 + '@solana/codecs-numbers@2.0.0-rc.1(typescript@5.6.3)': dependencies: '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) @@ -26698,6 +27125,14 @@ snapshots: '@solana/errors': 2.0.0-preview.2 fastestsmallesttextencoderdecoder: 1.0.22 + '@solana/codecs-strings@2.0.0-preview.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': + dependencies: + '@solana/codecs-core': 2.0.0-preview.4(typescript@5.6.3) + '@solana/codecs-numbers': 2.0.0-preview.4(typescript@5.6.3) + '@solana/errors': 2.0.0-preview.4(typescript@5.6.3) + fastestsmallesttextencoderdecoder: 1.0.22 + typescript: 5.6.3 + '@solana/codecs-strings@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': dependencies: '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) @@ -26716,6 +27151,17 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder + '@solana/codecs@2.0.0-preview.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': + dependencies: + '@solana/codecs-core': 2.0.0-preview.4(typescript@5.6.3) + '@solana/codecs-data-structures': 2.0.0-preview.4(typescript@5.6.3) + '@solana/codecs-numbers': 2.0.0-preview.4(typescript@5.6.3) + '@solana/codecs-strings': 2.0.0-preview.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + '@solana/options': 2.0.0-preview.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + typescript: 5.6.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + '@solana/codecs@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': dependencies: '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) @@ -26732,6 +27178,12 @@ snapshots: chalk: 5.3.0 commander: 12.1.0 + '@solana/errors@2.0.0-preview.4(typescript@5.6.3)': + dependencies: + chalk: 5.3.0 + commander: 12.1.0 + typescript: 5.6.3 + '@solana/errors@2.0.0-rc.1(typescript@5.6.3)': dependencies: chalk: 5.3.0 @@ -26743,6 +27195,17 @@ snapshots: '@solana/codecs-core': 2.0.0-preview.2 '@solana/codecs-numbers': 2.0.0-preview.2 + '@solana/options@2.0.0-preview.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': + dependencies: + '@solana/codecs-core': 2.0.0-preview.4(typescript@5.6.3) + '@solana/codecs-data-structures': 2.0.0-preview.4(typescript@5.6.3) + '@solana/codecs-numbers': 2.0.0-preview.4(typescript@5.6.3) + '@solana/codecs-strings': 2.0.0-preview.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + '@solana/errors': 2.0.0-preview.4(typescript@5.6.3) + typescript: 5.6.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + '@solana/options@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': dependencies: '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) @@ -26762,6 +27225,15 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder + '@solana/spl-token-group@0.0.5(@solana/web3.js@1.95.3(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-preview.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + '@solana/spl-type-length-value': 0.1.0 + '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - 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.6.3)': dependencies: '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) @@ -26770,6 +27242,14 @@ snapshots: - fastestsmallesttextencoderdecoder - typescript + '@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.95.3(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) + '@solana/web3.js': 1.95.3(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) @@ -26793,6 +27273,21 @@ snapshots: - typescript - utf-8-validate + '@solana/spl-token@0.4.8(@solana/web3.js@1.95.3(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 + '@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.5(@solana/web3.js@1.95.3(bufferutil@4.0.8)(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.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + '@solana/web3.js': 1.95.3(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 @@ -26825,6 +27320,28 @@ snapshots: '@wallet-standard/base': 1.1.0 '@wallet-standard/features': 1.1.0 + '@solana/web3.js@1.95.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)': + dependencies: + '@babel/runtime': 7.26.0 + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.1 + '@solana/buffer-layout': 4.0.1 + agentkeepalive: 4.5.0 + bigint-buffer: 1.1.5 + bn.js: 5.2.1 + borsh: 0.7.0 + bs58: 4.0.1 + buffer: 6.0.3 + fast-stable-stringify: 1.0.0 + jayson: 4.1.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + node-fetch: 2.7.0(encoding@0.1.13) + rpc-websockets: 9.0.4 + superstruct: 2.0.2 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + '@solana/web3.js@1.95.5(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)': dependencies: '@babel/runtime': 7.26.0 @@ -29241,6 +29758,8 @@ snapshots: dependencies: safe-buffer: 5.2.1 + base-x@4.0.0: {} + base-x@5.0.0: {} base64-arraybuffer@0.2.0: {} @@ -29424,6 +29943,8 @@ snapshots: borsh@1.0.0: {} + borsh@2.0.0: {} + bottleneck@2.19.5: {} bowser@2.11.0: {} @@ -29608,6 +30129,10 @@ snapshots: dependencies: base-x: 3.0.10 + bs58@5.0.0: + dependencies: + base-x: 4.0.0 + bs58@6.0.0: dependencies: base-x: 5.0.0 @@ -33077,6 +33602,11 @@ snapshots: graphemer@1.4.0: {} + graphemesplit@2.4.4: + dependencies: + js-base64: 3.7.7 + unicode-trie: 2.0.0 + graphql-request@6.1.0(encoding@0.1.13)(graphql@16.10.0): dependencies: '@graphql-typed-document-node/core': 3.2.0(graphql@16.10.0) @@ -33099,6 +33629,19 @@ snapshots: section-matter: 1.0.0 strip-bom-string: 1.0.0 + groq-sdk@0.5.0(encoding@0.1.13): + dependencies: + '@types/node': 18.19.68 + '@types/node-fetch': 2.6.12 + abort-controller: 3.0.0 + agentkeepalive: 4.5.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0(encoding@0.1.13) + web-streams-polyfill: 3.3.3 + transitivePeerDependencies: + - encoding + gtoken@7.1.0(encoding@0.1.13): dependencies: gaxios: 6.7.1(encoding@0.1.13) @@ -35059,7 +35602,7 @@ snapshots: inherits: 2.0.4 stream-splicer: 2.0.1 - langchain@0.3.6(@langchain/core@0.3.26(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(axios@1.7.9)(encoding@0.1.13)(handlebars@4.7.8)(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)): + langchain@0.3.6(@langchain/core@0.3.26(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(@langchain/groq@0.1.2(@langchain/core@0.3.26(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13))(axios@1.7.9)(encoding@0.1.13)(handlebars@4.7.8)(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)): dependencies: '@langchain/core': 0.3.26(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)) '@langchain/openai': 0.3.16(@langchain/core@0.3.26(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) @@ -35075,6 +35618,30 @@ snapshots: zod: 3.23.8 zod-to-json-schema: 3.24.1(zod@3.23.8) optionalDependencies: + '@langchain/groq': 0.1.2(@langchain/core@0.3.26(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) + axios: 1.7.9(debug@4.4.0) + handlebars: 4.7.8 + transitivePeerDependencies: + - encoding + - openai + + langchain@0.3.6(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)))(@langchain/groq@0.1.2(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13))(axios@1.7.9)(encoding@0.1.13)(handlebars@4.7.8)(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)): + dependencies: + '@langchain/core': 0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)) + '@langchain/openai': 0.3.16(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) + '@langchain/textsplitters': 0.1.0(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8))) + js-tiktoken: 1.0.15 + js-yaml: 4.1.0 + jsonpointer: 5.0.1 + langsmith: 0.2.13(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)) + openapi-types: 12.1.3 + p-retry: 4.6.2 + uuid: 10.0.0 + yaml: 2.6.1 + zod: 3.23.8 + zod-to-json-schema: 3.24.1(zod@3.23.8) + optionalDependencies: + '@langchain/groq': 0.1.2(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) axios: 1.7.9(debug@4.4.0) handlebars: 4.7.8 transitivePeerDependencies: @@ -35100,6 +35667,17 @@ snapshots: optionalDependencies: openai: 4.73.0(encoding@0.1.13)(zod@3.23.8) + langsmith@0.2.13(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)): + dependencies: + '@types/uuid': 10.0.0 + commander: 10.0.1 + p-queue: 6.6.2 + p-retry: 4.6.2 + semver: 7.6.3 + uuid: 10.0.0 + optionalDependencies: + openai: 4.77.0(encoding@0.1.13)(zod@3.23.8) + latest-version@7.0.0: dependencies: package-json: 8.1.1 @@ -39962,6 +40540,56 @@ snapshots: ip-address: 9.0.5 smart-buffer: 4.2.0 + solana-agent-kit@1.2.0(@noble/hashes@1.6.1)(axios@1.7.9)(bufferutil@4.0.8)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(handlebars@4.7.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8): + dependencies: + '@bonfida/spl-name-service': 3.0.7(@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) + '@coral-xyz/anchor': 0.29.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@langchain/core': 0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)) + '@langchain/groq': 0.1.2(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) + '@langchain/langgraph': 0.2.36(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8))) + '@langchain/openai': 0.3.16(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) + '@lightprotocol/compressed-token': 0.17.1(@lightprotocol/stateless.js@0.17.1(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) + '@lightprotocol/stateless.js': 0.17.1(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@metaplex-foundation/mpl-core': 1.1.1(@metaplex-foundation/umi@0.9.2)(@noble/hashes@1.6.1) + '@metaplex-foundation/mpl-token-metadata': 3.3.0(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-bundle-defaults': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(encoding@0.1.13) + '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)) + '@orca-so/common-sdk': 0.6.4(@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))(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(decimal.js@10.4.3) + '@orca-so/whirlpools-sdk': 0.13.12(@coral-xyz/anchor@0.29.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(@orca-so/common-sdk@0.6.4(@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))(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(decimal.js@10.4.3))(@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))(@solana/web3.js@1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(decimal.js@10.4.3) + '@raydium-io/raydium-sdk-v2': 0.1.95-alpha(bufferutil@4.0.8)(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.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': 1.95.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) + bn.js: 5.2.1 + bs58: 6.0.0 + decimal.js: 10.4.3 + dotenv: 16.4.7 + form-data: 4.0.1 + langchain: 0.3.6(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)))(@langchain/groq@0.1.2(@langchain/core@0.3.26(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13))(axios@1.7.9)(encoding@0.1.13)(handlebars@4.7.8)(openai@4.77.0(encoding@0.1.13)(zod@3.23.8)) + openai: 4.77.0(encoding@0.1.13)(zod@3.23.8) + typedoc: 0.26.11(typescript@5.6.3) + transitivePeerDependencies: + - '@langchain/anthropic' + - '@langchain/aws' + - '@langchain/cohere' + - '@langchain/google-genai' + - '@langchain/google-vertexai' + - '@langchain/mistralai' + - '@langchain/ollama' + - '@noble/hashes' + - axios + - bufferutil + - cheerio + - debug + - encoding + - fastestsmallesttextencoderdecoder + - handlebars + - peggy + - typeorm + - typescript + - utf-8-validate + - zod + solc@0.8.26(debug@4.4.0): dependencies: command-exists: 1.2.9 @@ -40625,6 +41253,8 @@ snapshots: tiny-emitter@2.1.0: {} + tiny-inflate@1.0.3: {} + tiny-invariant@1.3.3: {} tiny-warning@1.0.3: {} @@ -41208,6 +41838,11 @@ snapshots: unicode-property-aliases-ecmascript@2.1.0: {} + unicode-trie@2.0.0: + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + unicorn-magic@0.1.0: {} unified@11.0.5: From 7a34cd14e356019b3ebb9739260cadf9cf0e430a Mon Sep 17 00:00:00 2001 From: xiaohuo Date: Sun, 22 Dec 2024 20:06:19 +0800 Subject: [PATCH 2/7] fix: clean up --- .../src/evaluators/trust.ts | 543 -------- packages/plugin-solana-agentkit/src/index.ts | 20 +- .../src/providers/orderBook.ts | 45 - .../src/providers/simulationSellingService.ts | 501 -------- .../src/providers/token.ts | 1124 ----------------- .../src/providers/tokenUtils.ts | 72 -- .../src/providers/trustScoreProvider.ts | 740 ----------- .../src/providers/wallet.ts | 391 ------ 8 files changed, 4 insertions(+), 3432 deletions(-) delete mode 100644 packages/plugin-solana-agentkit/src/evaluators/trust.ts delete mode 100644 packages/plugin-solana-agentkit/src/providers/orderBook.ts delete mode 100644 packages/plugin-solana-agentkit/src/providers/simulationSellingService.ts delete mode 100644 packages/plugin-solana-agentkit/src/providers/token.ts delete mode 100644 packages/plugin-solana-agentkit/src/providers/tokenUtils.ts delete mode 100644 packages/plugin-solana-agentkit/src/providers/trustScoreProvider.ts delete mode 100644 packages/plugin-solana-agentkit/src/providers/wallet.ts diff --git a/packages/plugin-solana-agentkit/src/evaluators/trust.ts b/packages/plugin-solana-agentkit/src/evaluators/trust.ts deleted file mode 100644 index 2c4f441cf5..0000000000 --- a/packages/plugin-solana-agentkit/src/evaluators/trust.ts +++ /dev/null @@ -1,543 +0,0 @@ -import { - composeContext, - generateObjectArray, - generateTrueOrFalse, - MemoryManager, - booleanFooter, - ActionExample, - Content, - IAgentRuntime, - Memory, - 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"; - -const shouldProcessTemplate = - `# Task: Decide if the recent messages should be processed for token recommendations. - - Look for messages that: - - Mention specific token tickers or contract addresses - - Contain words related to buying, selling, or trading tokens - - Express opinions or convictions about tokens - - Based on the following conversation, should the messages be processed for recommendations? YES or NO - - {{recentMessages}} - - Should the messages be processed for recommendations? ` + booleanFooter; - -export const formatRecommendations = (recommendations: Memory[]) => { - const messageStrings = recommendations - .reverse() - .map((rec: Memory) => `${(rec.content as Content)?.content}`); - const finalMessageStrings = messageStrings.join("\n"); - return finalMessageStrings; -}; - -const recommendationTemplate = `TASK: Extract recommendations to buy or sell memecoins from the conversation as an array of objects in JSON format. - - Memecoins usually have a ticker and a contract address. Additionally, recommenders may make recommendations with some amount of conviction. The amount of conviction in their recommendation can be none, low, medium, or high. Recommenders can make recommendations to buy, not buy, sell and not sell. - -# START OF EXAMPLES -These are an examples of the expected output of this task: -{{evaluationExamples}} -# END OF EXAMPLES - -# INSTRUCTIONS - -Extract any new recommendations from the conversation that are not already present in the list of known recommendations below: -{{recentRecommendations}} - -- Include the recommender's username -- Try not to include already-known recommendations. If you think a recommendation is already known, but you're not sure, respond with alreadyKnown: true. -- Set the conviction to 'none', 'low', 'medium' or 'high' -- Set the recommendation type to 'buy', 'dont_buy', 'sell', or 'dont_sell' -- Include the contract address and/or ticker if available - -Recent Messages: -{{recentMessages}} - -Response should be a JSON object array inside a JSON markdown block. Correct response format: -\`\`\`json -[ - { - "recommender": string, - "ticker": string | null, - "contractAddress": string | null, - "type": enum, - "conviction": enum, - "alreadyKnown": boolean - }, - ... -] -\`\`\``; - -async function handler(runtime: IAgentRuntime, message: Memory) { - console.log("Evaluating for trust"); - const state = await runtime.composeState(message); - - const { agentId, roomId } = state; - - // Check if we should process the messages - const shouldProcessContext = composeContext({ - state, - template: shouldProcessTemplate, - }); - - const shouldProcess = await generateTrueOrFalse({ - context: shouldProcessContext, - modelClass: ModelClass.SMALL, - runtime, - }); - - if (!shouldProcess) { - console.log("Skipping process"); - return []; - } - - console.log("Processing recommendations"); - - // Get recent recommendations - const recommendationsManager = new MemoryManager({ - runtime, - tableName: "recommendations", - }); - - const recentRecommendations = await recommendationsManager.getMemories({ - roomId, - count: 20, - }); - - const context = composeContext({ - state: { - ...state, - recentRecommendations: formatRecommendations(recentRecommendations), - }, - template: recommendationTemplate, - }); - - const recommendations = await generateObjectArray({ - runtime, - context, - modelClass: ModelClass.LARGE, - }); - - console.log("recommendations", recommendations); - - if (!recommendations) { - return []; - } - - // If the recommendation is already known or corrupted, remove it - const filteredRecommendations = recommendations.filter((rec) => { - return ( - !rec.alreadyKnown && - (rec.ticker || rec.contractAddress) && - rec.recommender && - rec.conviction && - rec.recommender.trim() !== "" - ); - }); - - const { publicKey } = await getWalletKey(runtime, false); - - for (const rec of filteredRecommendations) { - // create the wallet provider and token provider - const walletProvider = new WalletProvider( - new Connection( - runtime.getSetting("RPC_URL") || - "https://api.mainnet-beta.solana.com" - ), - publicKey - ); - const tokenProvider = new TokenProvider( - rec.contractAddress, - walletProvider, - runtime.cacheManager - ); - - // TODO: Check to make sure the contract address is valid, it's the right one, etc - - // - if (!rec.contractAddress) { - const tokenAddress = await tokenProvider.getTokenFromWallet( - runtime, - rec.ticker - ); - rec.contractAddress = tokenAddress; - if (!tokenAddress) { - // try to search for the symbol and return the contract address with they highest liquidity and market cap - const result = await tokenProvider.searchDexScreenerData( - rec.ticker - ); - const tokenAddress = result?.baseToken?.address; - rec.contractAddress = tokenAddress; - if (!tokenAddress) { - console.warn("Could not find contract address for token"); - continue; - } - } - } - - // create the trust score manager - - const trustScoreDb = new TrustScoreDatabase(runtime.databaseAdapter.db); - const trustScoreManager = new TrustScoreManager( - runtime, - tokenProvider, - trustScoreDb - ); - - // get actors from the database - const participants = - await runtime.databaseAdapter.getParticipantsForRoom( - message.roomId - ); - - // find the first user Id from a user with the username that we extracted - const user = participants.find(async (actor) => { - const user = await runtime.databaseAdapter.getAccountById(actor); - return ( - user.name.toLowerCase().trim() === - rec.recommender.toLowerCase().trim() - ); - }); - - if (!user) { - console.warn("Could not find user: ", rec.recommender); - continue; - } - - const account = await runtime.databaseAdapter.getAccountById(user); - const userId = account.id; - - const recMemory = { - userId, - agentId, - content: { text: JSON.stringify(rec) }, - roomId, - createdAt: Date.now(), - }; - - await recommendationsManager.createMemory(recMemory, true); - - console.log("recommendationsManager", rec); - - // - from here we just need to make sure code is right - - // buy, dont buy, sell, dont sell - - const buyAmounts = await tokenProvider.calculateBuyAmounts(); - - let buyAmount = buyAmounts[rec.conviction.toLowerCase().trim()]; - if (!buyAmount) { - // handle annoying cases - // for now just put in 10 sol - buyAmount = 10; - } - - // TODO: is this is a buy, sell, dont buy, or dont sell? - const shouldTrade = await tokenProvider.shouldTradeToken(); - - if (!shouldTrade) { - console.warn( - "There might be a problem with the token, not trading" - ); - continue; - } - - switch (rec.type) { - case "buy": - // for now, lets just assume buy only, but we should implement - await trustScoreManager.createTradePerformance( - runtime, - rec.contractAddress, - userId, - { - buy_amount: rec.buyAmount, - is_simulation: true, - } - ); - break; - case "sell": - case "dont_sell": - case "dont_buy": - console.warn("Not implemented"); - break; - } - } - - return filteredRecommendations; -} - -export const trustEvaluator: Evaluator = { - name: "EXTRACT_RECOMMENDATIONS", - similes: [ - "GET_RECOMMENDATIONS", - "EXTRACT_TOKEN_RECS", - "EXTRACT_MEMECOIN_RECS", - ], - alwaysRun: true, - validate: async ( - runtime: IAgentRuntime, - message: Memory - ): Promise => { - if (message.content.text.length < 5) { - return false; - } - - return message.userId !== message.agentId; - }, - description: - "Extract recommendations to buy or sell memecoins/tokens from the conversation, including details like ticker, contract address, conviction level, and recommender username.", - handler, - examples: [ - { - context: `Actors in the scene: -{{user1}}: Experienced DeFi degen. Constantly chasing high yield farms. -{{user2}}: New to DeFi, learning the ropes. - -Recommendations about the actors: -None`, - messages: [ - { - user: "{{user1}}", - content: { - text: "Yo, have you checked out $SOLARUG? Dope new yield aggregator on Solana.", - }, - }, - { - user: "{{user2}}", - content: { - text: "Nah, I'm still trying to wrap my head around how yield farming even works haha. Is it risky?", - }, - }, - { - user: "{{user1}}", - content: { - text: "I mean, there's always risk in DeFi, but the $SOLARUG devs seem legit. Threw a few sol into the FCweoTfJ128jGgNEXgdfTXdEZVk58Bz9trCemr6sXNx9 vault, farming's been smooth so far.", - }, - }, - ] as ActionExample[], - outcome: `\`\`\`json -[ - { - "recommender": "{{user1}}", - "ticker": "SOLARUG", - "contractAddress": "FCweoTfJ128jGgNEXgdfTXdEZVk58Bz9trCemr6sXNx9", - "type": "buy", - "conviction": "medium", - "alreadyKnown": false - } -] -\`\`\``, - }, - - { - context: `Actors in the scene: -{{user1}}: Solana maximalist. Believes Solana will flip Ethereum. -{{user2}}: Multichain proponent. Holds both SOL and ETH. - -Recommendations about the actors: -{{user1}} has previously promoted $COPETOKEN and $SOYLENT.`, - messages: [ - { - user: "{{user1}}", - content: { - text: "If you're not long $SOLVAULT at 7tRzKud6FBVFEhYqZS3CuQ2orLRM21bdisGykL5Sr4Dx, you're missing out. This will be the blackhole of Solana liquidity.", - }, - }, - { - user: "{{user2}}", - content: { - text: "Idk man, feels like there's a new 'vault' or 'reserve' token every week on Sol. What happened to $COPETOKEN and $SOYLENT that you were shilling before?", - }, - }, - { - user: "{{user1}}", - content: { - text: "$COPETOKEN and $SOYLENT had their time, I took profits near the top. But $SOLVAULT is different, it has actual utility. Do what you want, but don't say I didn't warn you when this 50x's and you're left holding your $ETH bags.", - }, - }, - ] as ActionExample[], - outcome: `\`\`\`json -[ - { - "recommender": "{{user1}}", - "ticker": "COPETOKEN", - "contractAddress": null, - "type": "sell", - "conviction": "low", - "alreadyKnown": true - }, - { - "recommender": "{{user1}}", - "ticker": "SOYLENT", - "contractAddress": null, - "type": "sell", - "conviction": "low", - "alreadyKnown": true - }, - { - "recommender": "{{user1}}", - "ticker": "SOLVAULT", - "contractAddress": "7tRzKud6FBVFEhYqZS3CuQ2orLRM21bdisGykL5Sr4Dx", - "type": "buy", - "conviction": "high", - "alreadyKnown": false - } -] -\`\`\``, - }, - - { - context: `Actors in the scene: -{{user1}}: Self-proclaimed Solana alpha caller. Allegedly has insider info. -{{user2}}: Degen gambler. Will ape into any hyped token. - -Recommendations about the actors: -None`, - messages: [ - { - user: "{{user1}}", - content: { - text: "I normally don't do this, but I like you anon, so I'll let you in on some alpha. $ROULETTE at 48vV5y4DRH1Adr1bpvSgFWYCjLLPtHYBqUSwNc2cmCK2 is going to absolutely send it soon. You didn't hear it from me 🤐", - }, - }, - { - user: "{{user2}}", - content: { - text: "Oh shit, insider info from the alpha god himself? Say no more, I'm aping in hard.", - }, - }, - ] as ActionExample[], - outcome: `\`\`\`json -[ - { - "recommender": "{{user1}}", - "ticker": "ROULETTE", - "contractAddress": "48vV5y4DRH1Adr1bpvSgFWYCjLLPtHYBqUSwNc2cmCK2", - "type": "buy", - "conviction": "high", - "alreadyKnown": false - } -] -\`\`\``, - }, - - { - context: `Actors in the scene: -{{user1}}: NFT collector and trader. Bullish on Solana NFTs. -{{user2}}: Only invests based on fundamentals. Sees all NFTs as worthless JPEGs. - -Recommendations about the actors: -None -`, - messages: [ - { - user: "{{user1}}", - content: { - text: "GM. I'm heavily accumulating $PIXELAPE, the token for the Pixel Ape Yacht Club NFT collection. 10x is inevitable.", - }, - }, - { - user: "{{user2}}", - content: { - text: "NFTs are a scam bro. There's no underlying value. You're essentially trading worthless JPEGs.", - }, - }, - { - user: "{{user1}}", - content: { - text: "Fun staying poor 🤡 $PIXELAPE is about to moon and you'll be left behind.", - }, - }, - { - user: "{{user2}}", - content: { - text: "Whatever man, I'm not touching that shit with a ten foot pole. Have fun holding your bags.", - }, - }, - { - user: "{{user1}}", - content: { - text: "Don't need luck where I'm going 😎 Once $PIXELAPE at 3hAKKmR6XyBooQBPezCbUMhrmcyTkt38sRJm2thKytWc takes off, you'll change your tune.", - }, - }, - ], - outcome: `\`\`\`json -[ - { - "recommender": "{{user1}}", - "ticker": "PIXELAPE", - "contractAddress": "3hAKKmR6XyBooQBPezCbUMhrmcyTkt38sRJm2thKytWc", - "type": "buy", - "conviction": "high", - "alreadyKnown": false - } -] -\`\`\``, - }, - - { - context: `Actors in the scene: -{{user1}}: Contrarian investor. Bets against hyped projects. -{{user2}}: Trend follower. Buys tokens that are currently popular. - -Recommendations about the actors: -None`, - messages: [ - { - user: "{{user2}}", - content: { - text: "$SAMOYED is the talk of CT right now. Making serious moves. Might have to get a bag.", - }, - }, - { - user: "{{user1}}", - content: { - text: "Whenever a token is the 'talk of CT', that's my cue to short it. $SAMOYED is going to dump hard, mark my words.", - }, - }, - { - user: "{{user2}}", - content: { - text: "Idk man, the hype seems real this time. 5TQwHyZbedaH4Pcthj1Hxf5GqcigL6qWuB7YEsBtqvhr chart looks bullish af.", - }, - }, - { - user: "{{user1}}", - content: { - text: "Hype is always real until it isn't. I'm taking out a fat short position here. Don't say I didn't warn you when this crashes 90% and you're left holding the flaming bags.", - }, - }, - ], - outcome: `\`\`\`json -[ - { - "recommender": "{{user2}}", - "ticker": "SAMOYED", - "contractAddress": "5TQwHyZbedaH4Pcthj1Hxf5GqcigL6qWuB7YEsBtqvhr", - "type": "buy", - "conviction": "medium", - "alreadyKnown": false - }, - { - "recommender": "{{user1}}", - "ticker": "SAMOYED", - "contractAddress": "5TQwHyZbedaH4Pcthj1Hxf5GqcigL6qWuB7YEsBtqvhr", - "type": "dont_buy", - "conviction": "high", - "alreadyKnown": false - } -] -\`\`\``, - }, - ], -}; diff --git a/packages/plugin-solana-agentkit/src/index.ts b/packages/plugin-solana-agentkit/src/index.ts index 210b12787b..3a7749a528 100644 --- a/packages/plugin-solana-agentkit/src/index.ts +++ b/packages/plugin-solana-agentkit/src/index.ts @@ -1,24 +1,12 @@ -export * from "./providers/token.ts"; -export * from "./providers/wallet.ts"; -export * from "./providers/trustScoreProvider.ts"; -export * from "./evaluators/trust.ts"; - import { Plugin } from "@elizaos/core"; -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"; - -export { TokenProvider, WalletProvider }; +import createToken from "./actions/createToken.ts"; export const solanaAgentkitPlguin: Plugin = { name: "solana", description: "Solana Plugin with solana agent kit for Eliza", - actions: [transferToken], - evaluators: [trustEvaluator], - providers: [walletProvider, trustScoreProvider], + actions: [createToken], + evaluators: [], + providers: [,], }; export default solanaAgentkitPlguin; diff --git a/packages/plugin-solana-agentkit/src/providers/orderBook.ts b/packages/plugin-solana-agentkit/src/providers/orderBook.ts deleted file mode 100644 index ac4577e012..0000000000 --- a/packages/plugin-solana-agentkit/src/providers/orderBook.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { IAgentRuntime, Memory, Provider, State } from "@elizaos/core"; -interface Order { - userId: string; - ticker: string; - contractAddress: string; - timestamp: string; - buyAmount: number; - price: number; -} - -const orderBookProvider: Provider = { - get: async (runtime: IAgentRuntime, message: Memory, _state?: State) => { - const userId = message.userId; - - // Read the order book from the JSON file - const orderBookPath = - runtime.getSetting("orderBookPath") ?? "solana/orderBook"; - - const orderBook: Order[] = []; - - const cachedOrderBook = - await runtime.cacheManager.get(orderBookPath); - - if (cachedOrderBook) { - orderBook.push(...cachedOrderBook); - } - - // Filter the orders for the current user - const userOrders = orderBook.filter((order) => order.userId === userId); - - let totalProfit = 0; - for (const order of userOrders) { - // Get the current price of the asset (replace with actual price fetching logic) - const currentPrice = 120; - - const priceDifference = currentPrice - order.price; - const orderProfit = priceDifference * order.buyAmount; - totalProfit += orderProfit; - } - - return `The user has made a total profit of $${totalProfit.toFixed(2)} for the agent based on their recorded buy orders.`; - }, -}; - -export { orderBookProvider }; diff --git a/packages/plugin-solana-agentkit/src/providers/simulationSellingService.ts b/packages/plugin-solana-agentkit/src/providers/simulationSellingService.ts deleted file mode 100644 index 670eeb74f3..0000000000 --- a/packages/plugin-solana-agentkit/src/providers/simulationSellingService.ts +++ /dev/null @@ -1,501 +0,0 @@ -import { - TrustScoreDatabase, - TokenPerformance, - // TradePerformance, - TokenRecommendation, -} from "@elizaos/plugin-trustdb"; -import { Connection, PublicKey } from "@solana/web3.js"; -// Assuming TokenProvider and IAgentRuntime are available -import { TokenProvider } from "./token.ts"; -// import { settings } from "@elizaos/core"; -import { IAgentRuntime } from "@elizaos/core"; -import { WalletProvider } from "./wallet.ts"; -import * as amqp from "amqplib"; -import { ProcessedTokenData } from "../types/token.ts"; -import { getWalletKey } from "../keypairUtils.ts"; - -interface SellDetails { - sell_amount: number; - sell_recommender_id: string | null; -} - -export class SimulationSellingService { - private trustScoreDb: TrustScoreDatabase; - private walletProvider: WalletProvider; - private connection: Connection; - private baseMint: PublicKey; - private DECAY_RATE = 0.95; - private MAX_DECAY_DAYS = 30; - private backend: string; - private backendToken: string; - private amqpConnection: amqp.Connection; - private amqpChannel: amqp.Channel; - private sonarBe: string; - private sonarBeToken: string; - private runtime: IAgentRuntime; - - private runningProcesses: Set = new Set(); - - constructor(runtime: IAgentRuntime, trustScoreDb: TrustScoreDatabase) { - this.trustScoreDb = trustScoreDb; - - this.connection = new Connection(runtime.getSetting("RPC_URL")); - this.initializeWalletProvider(); - this.baseMint = new PublicKey( - runtime.getSetting("BASE_MINT") || - "So11111111111111111111111111111111111111112" - ); - this.backend = runtime.getSetting("BACKEND_URL"); - this.backendToken = runtime.getSetting("BACKEND_TOKEN"); - this.initializeRabbitMQ(runtime.getSetting("AMQP_URL")); - this.sonarBe = runtime.getSetting("SONAR_BE"); - this.sonarBeToken = runtime.getSetting("SONAR_BE_TOKEN"); - this.runtime = runtime; - } - /** - * Initializes the RabbitMQ connection and starts consuming messages. - * @param amqpUrl The RabbitMQ server URL. - */ - private async initializeRabbitMQ(amqpUrl: string) { - try { - this.amqpConnection = await amqp.connect(amqpUrl); - this.amqpChannel = await this.amqpConnection.createChannel(); - console.log("Connected to RabbitMQ"); - // Start consuming messages - this.consumeMessages(); - } catch (error) { - console.error("Failed to connect to RabbitMQ:", error); - } - } - - /** - * Sets up the consumer for the specified RabbitMQ queue. - */ - private async consumeMessages() { - const queue = "process_eliza_simulation"; - await this.amqpChannel.assertQueue(queue, { durable: true }); - this.amqpChannel.consume( - queue, - (msg) => { - if (msg !== null) { - const content = msg.content.toString(); - this.processMessage(content); - this.amqpChannel.ack(msg); - } - }, - { noAck: false } - ); - console.log(`Listening for messages on queue: ${queue}`); - } - - /** - * Processes incoming messages from RabbitMQ. - * @param message The message content as a string. - */ - private async processMessage(message: string) { - try { - const { tokenAddress, amount, sell_recommender_id } = - JSON.parse(message); - console.log( - `Received message for token ${tokenAddress} to sell ${amount}` - ); - - const decision: SellDecision = { - tokenPerformance: - await this.trustScoreDb.getTokenPerformance(tokenAddress), - amountToSell: amount, - sell_recommender_id: sell_recommender_id, - }; - - // Execute the sell - await this.executeSellDecision(decision); - - // Remove from running processes after completion - this.runningProcesses.delete(tokenAddress); - } catch (error) { - console.error("Error processing message:", error); - } - } - - /** - * Executes a single sell decision. - * @param decision The sell decision containing token performance and amount to sell. - */ - private async executeSellDecision(decision: SellDecision) { - const { tokenPerformance, amountToSell, sell_recommender_id } = - decision; - const tokenAddress = tokenPerformance.tokenAddress; - - try { - console.log( - `Executing sell for token ${tokenPerformance.symbol}: ${amountToSell}` - ); - - // Update the sell details - const sellDetails: SellDetails = { - sell_amount: amountToSell, - sell_recommender_id: sell_recommender_id, // Adjust if necessary - }; - const sellTimeStamp = new Date().toISOString(); - const tokenProvider = new TokenProvider( - tokenAddress, - this.walletProvider, - this.runtime.cacheManager - ); - - // Update sell details in the database - const sellDetailsData = await this.updateSellDetails( - tokenAddress, - sell_recommender_id, - sellTimeStamp, - sellDetails, - true, // isSimulation - tokenProvider - ); - - console.log("Sell order executed successfully", sellDetailsData); - - // check if balance is zero and remove token from running processes - const balance = this.trustScoreDb.getTokenBalance(tokenAddress); - if (balance === 0) { - this.runningProcesses.delete(tokenAddress); - } - // stop the process in the sonar backend - await this.stopProcessInTheSonarBackend(tokenAddress); - } catch (error) { - console.error( - `Error executing sell for token ${tokenAddress}:`, - error - ); - } - } - - /** - * Derives the public key based on the TEE (Trusted Execution Environment) mode and initializes the wallet provider. - * If TEE mode is enabled, derives a keypair using the DeriveKeyProvider with the wallet secret salt and agent ID. - * If TEE mode is disabled, uses the provided Solana public key or wallet public key from settings. - */ - private async initializeWalletProvider(): Promise { - const { publicKey } = await getWalletKey(this.runtime, false); - - this.walletProvider = new WalletProvider(this.connection, publicKey); - } - - public async startService() { - // starting the service - console.log("Starting SellingService..."); - await this.startListeners(); - } - - public async startListeners() { - // scanning recommendations and selling - console.log("Scanning for token performances..."); - const tokenPerformances = - await this.trustScoreDb.getAllTokenPerformancesWithBalance(); - - await this.processTokenPerformances(tokenPerformances); - } - - private processTokenPerformances(tokenPerformances: TokenPerformance[]) { - // To Do: logic when to sell and how much - console.log("Deciding when to sell and how much..."); - const runningProcesses = this.runningProcesses; - // remove running processes from tokenPerformances - tokenPerformances = tokenPerformances.filter( - (tp) => !runningProcesses.has(tp.tokenAddress) - ); - - // start the process in the sonar backend - tokenPerformances.forEach(async (tokenPerformance) => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const tokenProvider = new TokenProvider( - tokenPerformance.tokenAddress, - this.walletProvider, - this.runtime.cacheManager - ); - // const shouldTrade = await tokenProvider.shouldTradeToken(); - // if (shouldTrade) { - const tokenRecommendations: TokenRecommendation[] = - this.trustScoreDb.getRecommendationsByToken( - tokenPerformance.tokenAddress - ); - const tokenRecommendation: TokenRecommendation = - tokenRecommendations[0]; - const balance = tokenPerformance.balance; - const sell_recommender_id = tokenRecommendation.recommenderId; - const tokenAddress = tokenPerformance.tokenAddress; - const process = await this.startProcessInTheSonarBackend( - tokenAddress, - balance, - true, - sell_recommender_id, - tokenPerformance.initialMarketCap - ); - if (process) { - this.runningProcesses.add(tokenAddress); - } - // } - }); - } - - public processTokenPerformance( - tokenAddress: string, - recommenderId: string - ) { - try { - const runningProcesses = this.runningProcesses; - // check if token is already being processed - if (runningProcesses.has(tokenAddress)) { - console.log(`Token ${tokenAddress} is already being processed`); - return; - } - const tokenPerformance = - this.trustScoreDb.getTokenPerformance(tokenAddress); - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const tokenProvider = new TokenProvider( - tokenPerformance.tokenAddress, - this.walletProvider, - this.runtime.cacheManager - ); - const balance = tokenPerformance.balance; - const sell_recommender_id = recommenderId; - const process = this.startProcessInTheSonarBackend( - tokenAddress, - balance, - true, - sell_recommender_id, - tokenPerformance.initialMarketCap - ); - if (process) { - this.runningProcesses.add(tokenAddress); - } - } catch (error) { - console.error( - `Error getting token performance for token ${tokenAddress}:`, - error - ); - } - } - - private async startProcessInTheSonarBackend( - tokenAddress: string, - balance: number, - isSimulation: boolean, - sell_recommender_id: string, - initial_mc: number - ) { - try { - const message = JSON.stringify({ - tokenAddress, - balance, - isSimulation, - initial_mc, - sell_recommender_id, - }); - const response = await fetch( - `${this.sonarBe}/elizaos-sol/startProcess`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - "x-api-key": `${this.sonarBeToken}`, - }, - body: message, - } - ); - - if (!response.ok) { - console.error( - `Failed to send message to process token ${tokenAddress}` - ); - return; - } - - const result = await response.json(); - console.log("Received response:", result); - console.log(`Sent message to process token ${tokenAddress}`); - - return result; - } catch (error) { - console.error( - `Error sending message to process token ${tokenAddress}:`, - error - ); - return null; - } - } - - private stopProcessInTheSonarBackend(tokenAddress: string) { - try { - return fetch(`${this.sonarBe}/elizaos-sol/stopProcess`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "x-api-key": `${this.sonarBeToken}`, - }, - body: JSON.stringify({ tokenAddress }), - }); - } catch (error) { - console.error( - `Error stopping process for token ${tokenAddress}:`, - error - ); - } - } - - async updateSellDetails( - tokenAddress: string, - recommenderId: string, - sellTimeStamp: string, - sellDetails: SellDetails, - isSimulation: boolean, - tokenProvider: TokenProvider - ) { - const recommender = - await this.trustScoreDb.getOrCreateRecommenderWithTelegramId( - recommenderId - ); - const processedData: ProcessedTokenData = - await tokenProvider.getProcessedTokenData(); - const prices = await this.walletProvider.fetchPrices(null); - const solPrice = prices.solana.usd; - const sellSol = sellDetails.sell_amount / parseFloat(solPrice); - const sell_value_usd = - sellDetails.sell_amount * processedData.tradeData.price; - const trade = await this.trustScoreDb.getLatestTradePerformance( - tokenAddress, - recommender.id, - isSimulation - ); - const buyTimeStamp = trade.buy_timeStamp; - const marketCap = - processedData.dexScreenerData.pairs[0]?.marketCap || 0; - const liquidity = - processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0; - const sell_price = processedData.tradeData.price; - const profit_usd = sell_value_usd - trade.buy_value_usd; - const profit_percent = (profit_usd / trade.buy_value_usd) * 100; - - const market_cap_change = marketCap - trade.buy_market_cap; - const liquidity_change = liquidity - trade.buy_liquidity; - - const isRapidDump = await this.isRapidDump(tokenAddress, tokenProvider); - - const sellDetailsData = { - sell_price: sell_price, - sell_timeStamp: sellTimeStamp, - sell_amount: sellDetails.sell_amount, - received_sol: sellSol, - sell_value_usd: sell_value_usd, - profit_usd: profit_usd, - profit_percent: profit_percent, - sell_market_cap: marketCap, - market_cap_change: market_cap_change, - sell_liquidity: liquidity, - liquidity_change: liquidity_change, - rapidDump: isRapidDump, - sell_recommender_id: sellDetails.sell_recommender_id || null, - }; - this.trustScoreDb.updateTradePerformanceOnSell( - tokenAddress, - recommender.id, - buyTimeStamp, - sellDetailsData, - isSimulation - ); - - // If the trade is a simulation update the balance - const oldBalance = this.trustScoreDb.getTokenBalance(tokenAddress); - const tokenBalance = oldBalance - sellDetails.sell_amount; - this.trustScoreDb.updateTokenBalance(tokenAddress, tokenBalance); - // generate some random hash for simulations - const hash = Math.random().toString(36).substring(7); - const transaction = { - tokenAddress: tokenAddress, - type: "sell" as "buy" | "sell", - transactionHash: hash, - amount: sellDetails.sell_amount, - price: processedData.tradeData.price, - isSimulation: true, - timestamp: new Date().toISOString(), - }; - this.trustScoreDb.addTransaction(transaction); - this.updateTradeInBe( - tokenAddress, - recommender.id, - recommender.telegramId, - sellDetailsData, - tokenBalance - ); - - return sellDetailsData; - } - async isRapidDump( - tokenAddress: string, - tokenProvider: TokenProvider - ): Promise { - const processedData: ProcessedTokenData = - await tokenProvider.getProcessedTokenData(); - console.log(`Fetched processed token data for token: ${tokenAddress}`); - - return processedData.tradeData.trade_24h_change_percent < -50; - } - - async delay(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - - async updateTradeInBe( - tokenAddress: string, - recommenderId: string, - username: string, - data: SellDetails, - balanceLeft: number, - retries = 3, - delayMs = 2000 - ) { - for (let attempt = 1; attempt <= retries; attempt++) { - try { - await fetch( - `${this.backend}/api/updaters/updateTradePerformance`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${this.backendToken}`, - }, - body: JSON.stringify({ - tokenAddress: tokenAddress, - tradeData: data, - recommenderId: recommenderId, - username: username, - isSimulation: true, - balanceLeft: balanceLeft, - }), - } - ); - // If the request is successful, exit the loop - return; - } catch (error) { - console.error( - `Attempt ${attempt} failed: Error creating trade in backend`, - error - ); - if (attempt < retries) { - console.log(`Retrying in ${delayMs} ms...`); - await this.delay(delayMs); // Wait for the specified delay before retrying - } else { - console.error("All attempts failed."); - } - } - } - } -} - -// SellDecision interface -interface SellDecision { - tokenPerformance: TokenPerformance; - amountToSell: number; - sell_recommender_id: string | null; -} diff --git a/packages/plugin-solana-agentkit/src/providers/token.ts b/packages/plugin-solana-agentkit/src/providers/token.ts deleted file mode 100644 index d8e885915a..0000000000 --- a/packages/plugin-solana-agentkit/src/providers/token.ts +++ /dev/null @@ -1,1124 +0,0 @@ -import { ICacheManager, settings } from "@elizaos/core"; -import { IAgentRuntime, Memory, Provider, State } from "@elizaos/core"; -import { - DexScreenerData, - DexScreenerPair, - HolderData, - ProcessedTokenData, - TokenSecurityData, - TokenTradeData, - CalculatedBuyAmounts, - Prices, - TokenCodex, -} from "../types/token.ts"; -import NodeCache from "node-cache"; -import * as path from "path"; -import { toBN } from "../bignumber.ts"; -import { WalletProvider, Item } from "./wallet.ts"; -import { Connection } from "@solana/web3.js"; -import { getWalletKey } from "../keypairUtils.ts"; - -const PROVIDER_CONFIG = { - BIRDEYE_API: "https://public-api.birdeye.so", - MAX_RETRIES: 3, - RETRY_DELAY: 2000, - DEFAULT_RPC: "https://api.mainnet-beta.solana.com", - TOKEN_ADDRESSES: { - SOL: "So11111111111111111111111111111111111111112", - BTC: "qfnqNqs3nCAHjnyCgLRDbBtq4p2MtHZxw8YjSyYhPoL", - ETH: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", - Example: "2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh", - }, - TOKEN_SECURITY_ENDPOINT: "/defi/token_security?address=", - TOKEN_TRADE_DATA_ENDPOINT: "/defi/v3/token/trade-data/single?address=", - DEX_SCREENER_API: "https://api.dexscreener.com/latest/dex/tokens/", - MAIN_WALLET: "", -}; - -export class TokenProvider { - private cache: NodeCache; - private cacheKey: string = "solana/tokens"; - private NETWORK_ID = 1399811149; - private GRAPHQL_ENDPOINT = "https://graph.codex.io/graphql"; - - constructor( - // private connection: Connection, - private tokenAddress: string, - private walletProvider: WalletProvider, - private cacheManager: ICacheManager - ) { - this.cache = new NodeCache({ stdTTL: 300 }); // 5 minutes cache - } - - private async readFromCache(key: string): Promise { - const cached = await this.cacheManager.get( - path.join(this.cacheKey, key) - ); - return cached; - } - - private async writeToCache(key: string, data: T): Promise { - await this.cacheManager.set(path.join(this.cacheKey, key), data, { - expires: Date.now() + 5 * 60 * 1000, - }); - } - - private async getCachedData(key: string): Promise { - // Check in-memory cache first - const cachedData = this.cache.get(key); - if (cachedData) { - return cachedData; - } - - // Check file-based cache - const fileCachedData = await this.readFromCache(key); - if (fileCachedData) { - // Populate in-memory cache - this.cache.set(key, fileCachedData); - return fileCachedData; - } - - return null; - } - - private async setCachedData(cacheKey: string, data: T): Promise { - // Set in-memory cache - this.cache.set(cacheKey, data); - - // Write to file-based cache - await this.writeToCache(cacheKey, data); - } - - private async fetchWithRetry( - url: string, - options: RequestInit = {} - ): Promise { - let lastError: Error; - - for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) { - try { - const response = await fetch(url, { - ...options, - headers: { - Accept: "application/json", - "x-chain": "solana", - "X-API-KEY": settings.BIRDEYE_API_KEY || "", - ...options.headers, - }, - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error( - `HTTP error! status: ${response.status}, message: ${errorText}` - ); - } - - const data = await response.json(); - return data; - } catch (error) { - console.error(`Attempt ${i + 1} failed:`, error); - lastError = error as Error; - if (i < PROVIDER_CONFIG.MAX_RETRIES - 1) { - const delay = PROVIDER_CONFIG.RETRY_DELAY * Math.pow(2, i); - console.log(`Waiting ${delay}ms before retrying...`); - await new Promise((resolve) => setTimeout(resolve, delay)); - continue; - } - } - } - - console.error( - "All attempts failed. Throwing the last error:", - lastError - ); - throw lastError; - } - - async getTokensInWallet(runtime: IAgentRuntime): Promise { - const walletInfo = - await this.walletProvider.fetchPortfolioValue(runtime); - const items = walletInfo.items; - return items; - } - - // check if the token symbol is in the wallet - async getTokenFromWallet(runtime: IAgentRuntime, tokenSymbol: string) { - try { - const items = await this.getTokensInWallet(runtime); - const token = items.find((item) => item.symbol === tokenSymbol); - - if (token) { - return token.address; - } else { - return null; - } - } catch (error) { - console.error("Error checking token in wallet:", error); - return null; - } - } - - async fetchTokenCodex(): Promise { - try { - const cacheKey = `token_${this.tokenAddress}`; - const cachedData = this.getCachedData(cacheKey); - if (cachedData) { - console.log( - `Returning cached token data for ${this.tokenAddress}.` - ); - return cachedData; - } - const query = ` - query Token($address: String!, $networkId: Int!) { - token(input: { address: $address, networkId: $networkId }) { - id - address - cmcId - decimals - name - symbol - totalSupply - isScam - info { - circulatingSupply - imageThumbUrl - } - explorerData { - blueCheckmark - description - tokenType - } - } - } - `; - - const variables = { - address: this.tokenAddress, - networkId: this.NETWORK_ID, // Replace with your network ID - }; - - const response = await fetch(this.GRAPHQL_ENDPOINT, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: settings.CODEX_API_KEY, - }, - body: JSON.stringify({ - query, - variables, - }), - }).then((res) => res.json()); - - const token = response.data?.data?.token; - - if (!token) { - throw new Error(`No data returned for token ${tokenAddress}`); - } - - this.setCachedData(cacheKey, token); - - return { - id: token.id, - address: token.address, - cmcId: token.cmcId, - decimals: token.decimals, - name: token.name, - symbol: token.symbol, - totalSupply: token.totalSupply, - circulatingSupply: token.info?.circulatingSupply, - imageThumbUrl: token.info?.imageThumbUrl, - blueCheckmark: token.explorerData?.blueCheckmark, - isScam: token.isScam ? true : false, - }; - } catch (error) { - console.error( - "Error fetching token data from Codex:", - error.message - ); - return {} as TokenCodex; - } - } - - async fetchPrices(): Promise { - try { - const cacheKey = "prices"; - const cachedData = this.getCachedData(cacheKey); - if (cachedData) { - console.log("Returning cached prices."); - return cachedData; - } - const { SOL, BTC, ETH } = PROVIDER_CONFIG.TOKEN_ADDRESSES; - const tokens = [SOL, BTC, ETH]; - const prices: Prices = { - solana: { usd: "0" }, - bitcoin: { usd: "0" }, - ethereum: { usd: "0" }, - }; - - for (const token of tokens) { - const response = await this.fetchWithRetry( - `${PROVIDER_CONFIG.BIRDEYE_API}/defi/price?address=${token}`, - { - headers: { - "x-chain": "solana", - }, - } - ); - - if (response?.data?.value) { - const price = response.data.value.toString(); - prices[ - token === SOL - ? "solana" - : token === BTC - ? "bitcoin" - : "ethereum" - ].usd = price; - } else { - console.warn(`No price data available for token: ${token}`); - } - } - this.setCachedData(cacheKey, prices); - return prices; - } catch (error) { - console.error("Error fetching prices:", error); - throw error; - } - } - async calculateBuyAmounts(): Promise { - const dexScreenerData = await this.fetchDexScreenerData(); - const prices = await this.fetchPrices(); - const solPrice = toBN(prices.solana.usd); - - if (!dexScreenerData || dexScreenerData.pairs.length === 0) { - return { none: 0, low: 0, medium: 0, high: 0 }; - } - - // Get the first pair - const pair = dexScreenerData.pairs[0]; - const { liquidity, marketCap } = pair; - if (!liquidity || !marketCap) { - return { none: 0, low: 0, medium: 0, high: 0 }; - } - - if (liquidity.usd === 0) { - return { none: 0, low: 0, medium: 0, high: 0 }; - } - if (marketCap < 100000) { - return { none: 0, low: 0, medium: 0, high: 0 }; - } - - // impact percentages based on liquidity - const impactPercentages = { - LOW: 0.01, // 1% of liquidity - MEDIUM: 0.05, // 5% of liquidity - HIGH: 0.1, // 10% of liquidity - }; - - // Calculate buy amounts in USD - const lowBuyAmountUSD = liquidity.usd * impactPercentages.LOW; - const mediumBuyAmountUSD = liquidity.usd * impactPercentages.MEDIUM; - const highBuyAmountUSD = liquidity.usd * impactPercentages.HIGH; - - // Convert each buy amount to SOL - const lowBuyAmountSOL = toBN(lowBuyAmountUSD).div(solPrice).toNumber(); - const mediumBuyAmountSOL = toBN(mediumBuyAmountUSD) - .div(solPrice) - .toNumber(); - const highBuyAmountSOL = toBN(highBuyAmountUSD) - .div(solPrice) - .toNumber(); - - return { - none: 0, - low: lowBuyAmountSOL, - medium: mediumBuyAmountSOL, - high: highBuyAmountSOL, - }; - } - - async fetchTokenSecurity(): Promise { - const cacheKey = `tokenSecurity_${this.tokenAddress}`; - const cachedData = this.getCachedData(cacheKey); - if (cachedData) { - console.log( - `Returning cached token security data for ${this.tokenAddress}.` - ); - return cachedData; - } - const url = `${PROVIDER_CONFIG.BIRDEYE_API}${PROVIDER_CONFIG.TOKEN_SECURITY_ENDPOINT}${this.tokenAddress}`; - const data = await this.fetchWithRetry(url); - - if (!data?.success || !data?.data) { - throw new Error("No token security data available"); - } - - const security: TokenSecurityData = { - ownerBalance: data.data.ownerBalance, - creatorBalance: data.data.creatorBalance, - ownerPercentage: data.data.ownerPercentage, - creatorPercentage: data.data.creatorPercentage, - top10HolderBalance: data.data.top10HolderBalance, - top10HolderPercent: data.data.top10HolderPercent, - }; - this.setCachedData(cacheKey, security); - console.log(`Token security data cached for ${this.tokenAddress}.`); - - return security; - } - - async fetchTokenTradeData(): Promise { - const cacheKey = `tokenTradeData_${this.tokenAddress}`; - const cachedData = this.getCachedData(cacheKey); - if (cachedData) { - console.log( - `Returning cached token trade data for ${this.tokenAddress}.` - ); - return cachedData; - } - - const url = `${PROVIDER_CONFIG.BIRDEYE_API}${PROVIDER_CONFIG.TOKEN_TRADE_DATA_ENDPOINT}${this.tokenAddress}`; - const options = { - method: "GET", - headers: { - accept: "application/json", - "X-API-KEY": settings.BIRDEYE_API_KEY || "", - }, - }; - - const data = await fetch(url, options) - .then((res) => res.json()) - .catch((err) => console.error(err)); - - if (!data?.success || !data?.data) { - throw new Error("No token trade data available"); - } - - const tradeData: TokenTradeData = { - address: data.data.address, - holder: data.data.holder, - market: data.data.market, - last_trade_unix_time: data.data.last_trade_unix_time, - last_trade_human_time: data.data.last_trade_human_time, - price: data.data.price, - history_30m_price: data.data.history_30m_price, - price_change_30m_percent: data.data.price_change_30m_percent, - history_1h_price: data.data.history_1h_price, - price_change_1h_percent: data.data.price_change_1h_percent, - history_2h_price: data.data.history_2h_price, - price_change_2h_percent: data.data.price_change_2h_percent, - history_4h_price: data.data.history_4h_price, - price_change_4h_percent: data.data.price_change_4h_percent, - history_6h_price: data.data.history_6h_price, - price_change_6h_percent: data.data.price_change_6h_percent, - history_8h_price: data.data.history_8h_price, - price_change_8h_percent: data.data.price_change_8h_percent, - history_12h_price: data.data.history_12h_price, - price_change_12h_percent: data.data.price_change_12h_percent, - history_24h_price: data.data.history_24h_price, - price_change_24h_percent: data.data.price_change_24h_percent, - unique_wallet_30m: data.data.unique_wallet_30m, - unique_wallet_history_30m: data.data.unique_wallet_history_30m, - unique_wallet_30m_change_percent: - data.data.unique_wallet_30m_change_percent, - unique_wallet_1h: data.data.unique_wallet_1h, - unique_wallet_history_1h: data.data.unique_wallet_history_1h, - unique_wallet_1h_change_percent: - data.data.unique_wallet_1h_change_percent, - unique_wallet_2h: data.data.unique_wallet_2h, - unique_wallet_history_2h: data.data.unique_wallet_history_2h, - unique_wallet_2h_change_percent: - data.data.unique_wallet_2h_change_percent, - unique_wallet_4h: data.data.unique_wallet_4h, - unique_wallet_history_4h: data.data.unique_wallet_history_4h, - unique_wallet_4h_change_percent: - data.data.unique_wallet_4h_change_percent, - unique_wallet_8h: data.data.unique_wallet_8h, - unique_wallet_history_8h: data.data.unique_wallet_history_8h, - unique_wallet_8h_change_percent: - data.data.unique_wallet_8h_change_percent, - unique_wallet_24h: data.data.unique_wallet_24h, - unique_wallet_history_24h: data.data.unique_wallet_history_24h, - unique_wallet_24h_change_percent: - data.data.unique_wallet_24h_change_percent, - trade_30m: data.data.trade_30m, - trade_history_30m: data.data.trade_history_30m, - trade_30m_change_percent: data.data.trade_30m_change_percent, - sell_30m: data.data.sell_30m, - sell_history_30m: data.data.sell_history_30m, - sell_30m_change_percent: data.data.sell_30m_change_percent, - buy_30m: data.data.buy_30m, - buy_history_30m: data.data.buy_history_30m, - buy_30m_change_percent: data.data.buy_30m_change_percent, - volume_30m: data.data.volume_30m, - volume_30m_usd: data.data.volume_30m_usd, - volume_history_30m: data.data.volume_history_30m, - volume_history_30m_usd: data.data.volume_history_30m_usd, - volume_30m_change_percent: data.data.volume_30m_change_percent, - volume_buy_30m: data.data.volume_buy_30m, - volume_buy_30m_usd: data.data.volume_buy_30m_usd, - volume_buy_history_30m: data.data.volume_buy_history_30m, - volume_buy_history_30m_usd: data.data.volume_buy_history_30m_usd, - volume_buy_30m_change_percent: - data.data.volume_buy_30m_change_percent, - volume_sell_30m: data.data.volume_sell_30m, - volume_sell_30m_usd: data.data.volume_sell_30m_usd, - volume_sell_history_30m: data.data.volume_sell_history_30m, - volume_sell_history_30m_usd: data.data.volume_sell_history_30m_usd, - volume_sell_30m_change_percent: - data.data.volume_sell_30m_change_percent, - trade_1h: data.data.trade_1h, - trade_history_1h: data.data.trade_history_1h, - trade_1h_change_percent: data.data.trade_1h_change_percent, - sell_1h: data.data.sell_1h, - sell_history_1h: data.data.sell_history_1h, - sell_1h_change_percent: data.data.sell_1h_change_percent, - buy_1h: data.data.buy_1h, - buy_history_1h: data.data.buy_history_1h, - buy_1h_change_percent: data.data.buy_1h_change_percent, - volume_1h: data.data.volume_1h, - volume_1h_usd: data.data.volume_1h_usd, - volume_history_1h: data.data.volume_history_1h, - volume_history_1h_usd: data.data.volume_history_1h_usd, - volume_1h_change_percent: data.data.volume_1h_change_percent, - volume_buy_1h: data.data.volume_buy_1h, - volume_buy_1h_usd: data.data.volume_buy_1h_usd, - volume_buy_history_1h: data.data.volume_buy_history_1h, - volume_buy_history_1h_usd: data.data.volume_buy_history_1h_usd, - volume_buy_1h_change_percent: - data.data.volume_buy_1h_change_percent, - volume_sell_1h: data.data.volume_sell_1h, - volume_sell_1h_usd: data.data.volume_sell_1h_usd, - volume_sell_history_1h: data.data.volume_sell_history_1h, - volume_sell_history_1h_usd: data.data.volume_sell_history_1h_usd, - volume_sell_1h_change_percent: - data.data.volume_sell_1h_change_percent, - trade_2h: data.data.trade_2h, - trade_history_2h: data.data.trade_history_2h, - trade_2h_change_percent: data.data.trade_2h_change_percent, - sell_2h: data.data.sell_2h, - sell_history_2h: data.data.sell_history_2h, - sell_2h_change_percent: data.data.sell_2h_change_percent, - buy_2h: data.data.buy_2h, - buy_history_2h: data.data.buy_history_2h, - buy_2h_change_percent: data.data.buy_2h_change_percent, - volume_2h: data.data.volume_2h, - volume_2h_usd: data.data.volume_2h_usd, - volume_history_2h: data.data.volume_history_2h, - volume_history_2h_usd: data.data.volume_history_2h_usd, - volume_2h_change_percent: data.data.volume_2h_change_percent, - volume_buy_2h: data.data.volume_buy_2h, - volume_buy_2h_usd: data.data.volume_buy_2h_usd, - volume_buy_history_2h: data.data.volume_buy_history_2h, - volume_buy_history_2h_usd: data.data.volume_buy_history_2h_usd, - volume_buy_2h_change_percent: - data.data.volume_buy_2h_change_percent, - volume_sell_2h: data.data.volume_sell_2h, - volume_sell_2h_usd: data.data.volume_sell_2h_usd, - volume_sell_history_2h: data.data.volume_sell_history_2h, - volume_sell_history_2h_usd: data.data.volume_sell_history_2h_usd, - volume_sell_2h_change_percent: - data.data.volume_sell_2h_change_percent, - trade_4h: data.data.trade_4h, - trade_history_4h: data.data.trade_history_4h, - trade_4h_change_percent: data.data.trade_4h_change_percent, - sell_4h: data.data.sell_4h, - sell_history_4h: data.data.sell_history_4h, - sell_4h_change_percent: data.data.sell_4h_change_percent, - buy_4h: data.data.buy_4h, - buy_history_4h: data.data.buy_history_4h, - buy_4h_change_percent: data.data.buy_4h_change_percent, - volume_4h: data.data.volume_4h, - volume_4h_usd: data.data.volume_4h_usd, - volume_history_4h: data.data.volume_history_4h, - volume_history_4h_usd: data.data.volume_history_4h_usd, - volume_4h_change_percent: data.data.volume_4h_change_percent, - volume_buy_4h: data.data.volume_buy_4h, - volume_buy_4h_usd: data.data.volume_buy_4h_usd, - volume_buy_history_4h: data.data.volume_buy_history_4h, - volume_buy_history_4h_usd: data.data.volume_buy_history_4h_usd, - volume_buy_4h_change_percent: - data.data.volume_buy_4h_change_percent, - volume_sell_4h: data.data.volume_sell_4h, - volume_sell_4h_usd: data.data.volume_sell_4h_usd, - volume_sell_history_4h: data.data.volume_sell_history_4h, - volume_sell_history_4h_usd: data.data.volume_sell_history_4h_usd, - volume_sell_4h_change_percent: - data.data.volume_sell_4h_change_percent, - trade_8h: data.data.trade_8h, - trade_history_8h: data.data.trade_history_8h, - trade_8h_change_percent: data.data.trade_8h_change_percent, - sell_8h: data.data.sell_8h, - sell_history_8h: data.data.sell_history_8h, - sell_8h_change_percent: data.data.sell_8h_change_percent, - buy_8h: data.data.buy_8h, - buy_history_8h: data.data.buy_history_8h, - buy_8h_change_percent: data.data.buy_8h_change_percent, - volume_8h: data.data.volume_8h, - volume_8h_usd: data.data.volume_8h_usd, - volume_history_8h: data.data.volume_history_8h, - volume_history_8h_usd: data.data.volume_history_8h_usd, - volume_8h_change_percent: data.data.volume_8h_change_percent, - volume_buy_8h: data.data.volume_buy_8h, - volume_buy_8h_usd: data.data.volume_buy_8h_usd, - volume_buy_history_8h: data.data.volume_buy_history_8h, - volume_buy_history_8h_usd: data.data.volume_buy_history_8h_usd, - volume_buy_8h_change_percent: - data.data.volume_buy_8h_change_percent, - volume_sell_8h: data.data.volume_sell_8h, - volume_sell_8h_usd: data.data.volume_sell_8h_usd, - volume_sell_history_8h: data.data.volume_sell_history_8h, - volume_sell_history_8h_usd: data.data.volume_sell_history_8h_usd, - volume_sell_8h_change_percent: - data.data.volume_sell_8h_change_percent, - trade_24h: data.data.trade_24h, - trade_history_24h: data.data.trade_history_24h, - trade_24h_change_percent: data.data.trade_24h_change_percent, - sell_24h: data.data.sell_24h, - sell_history_24h: data.data.sell_history_24h, - sell_24h_change_percent: data.data.sell_24h_change_percent, - buy_24h: data.data.buy_24h, - buy_history_24h: data.data.buy_history_24h, - buy_24h_change_percent: data.data.buy_24h_change_percent, - volume_24h: data.data.volume_24h, - volume_24h_usd: data.data.volume_24h_usd, - volume_history_24h: data.data.volume_history_24h, - volume_history_24h_usd: data.data.volume_history_24h_usd, - volume_24h_change_percent: data.data.volume_24h_change_percent, - volume_buy_24h: data.data.volume_buy_24h, - volume_buy_24h_usd: data.data.volume_buy_24h_usd, - volume_buy_history_24h: data.data.volume_buy_history_24h, - volume_buy_history_24h_usd: data.data.volume_buy_history_24h_usd, - volume_buy_24h_change_percent: - data.data.volume_buy_24h_change_percent, - volume_sell_24h: data.data.volume_sell_24h, - volume_sell_24h_usd: data.data.volume_sell_24h_usd, - volume_sell_history_24h: data.data.volume_sell_history_24h, - volume_sell_history_24h_usd: data.data.volume_sell_history_24h_usd, - volume_sell_24h_change_percent: - data.data.volume_sell_24h_change_percent, - }; - this.setCachedData(cacheKey, tradeData); - return tradeData; - } - - async fetchDexScreenerData(): Promise { - const cacheKey = `dexScreenerData_${this.tokenAddress}`; - const cachedData = this.getCachedData(cacheKey); - if (cachedData) { - console.log("Returning cached DexScreener data."); - return cachedData; - } - - const url = `https://api.dexscreener.com/latest/dex/search?q=${this.tokenAddress}`; - try { - console.log( - `Fetching DexScreener data for token: ${this.tokenAddress}` - ); - const data = await fetch(url) - .then((res) => res.json()) - .catch((err) => { - console.error(err); - }); - - if (!data || !data.pairs) { - throw new Error("No DexScreener data available"); - } - - const dexData: DexScreenerData = { - schemaVersion: data.schemaVersion, - pairs: data.pairs, - }; - - // Cache the result - this.setCachedData(cacheKey, dexData); - - return dexData; - } catch (error) { - console.error(`Error fetching DexScreener data:`, error); - return { - schemaVersion: "1.0.0", - pairs: [], - }; - } - } - - async searchDexScreenerData( - symbol: string - ): Promise { - const cacheKey = `dexScreenerData_search_${symbol}`; - const cachedData = await this.getCachedData(cacheKey); - if (cachedData) { - console.log("Returning cached search DexScreener data."); - return this.getHighestLiquidityPair(cachedData); - } - - const url = `https://api.dexscreener.com/latest/dex/search?q=${symbol}`; - try { - console.log(`Fetching DexScreener data for symbol: ${symbol}`); - const data = await fetch(url) - .then((res) => res.json()) - .catch((err) => { - console.error(err); - return null; - }); - - if (!data || !data.pairs || data.pairs.length === 0) { - throw new Error("No DexScreener data available"); - } - - const dexData: DexScreenerData = { - schemaVersion: data.schemaVersion, - pairs: data.pairs, - }; - - // Cache the result - this.setCachedData(cacheKey, dexData); - - // Return the pair with the highest liquidity and market cap - return this.getHighestLiquidityPair(dexData); - } catch (error) { - console.error(`Error fetching DexScreener data:`, error); - return null; - } - } - getHighestLiquidityPair(dexData: DexScreenerData): DexScreenerPair | null { - if (dexData.pairs.length === 0) { - return null; - } - - // Sort pairs by both liquidity and market cap to get the highest one - return dexData.pairs.sort((a, b) => { - const liquidityDiff = b.liquidity.usd - a.liquidity.usd; - if (liquidityDiff !== 0) { - return liquidityDiff; // Higher liquidity comes first - } - return b.marketCap - a.marketCap; // If liquidity is equal, higher market cap comes first - })[0]; - } - - async analyzeHolderDistribution( - tradeData: TokenTradeData - ): Promise { - // Define the time intervals to consider (e.g., 30m, 1h, 2h) - const intervals = [ - { - period: "30m", - change: tradeData.unique_wallet_30m_change_percent, - }, - { period: "1h", change: tradeData.unique_wallet_1h_change_percent }, - { period: "2h", change: tradeData.unique_wallet_2h_change_percent }, - { period: "4h", change: tradeData.unique_wallet_4h_change_percent }, - { period: "8h", change: tradeData.unique_wallet_8h_change_percent }, - { - period: "24h", - change: tradeData.unique_wallet_24h_change_percent, - }, - ]; - - // Calculate the average change percentage - const validChanges = intervals - .map((interval) => interval.change) - .filter( - (change) => change !== null && change !== undefined - ) as number[]; - - if (validChanges.length === 0) { - return "stable"; - } - - const averageChange = - validChanges.reduce((acc, curr) => acc + curr, 0) / - validChanges.length; - - const increaseThreshold = 10; // e.g., average change > 10% - const decreaseThreshold = -10; // e.g., average change < -10% - - if (averageChange > increaseThreshold) { - return "increasing"; - } else if (averageChange < decreaseThreshold) { - return "decreasing"; - } else { - return "stable"; - } - } - - async fetchHolderList(): Promise { - const cacheKey = `holderList_${this.tokenAddress}`; - const cachedData = this.getCachedData(cacheKey); - if (cachedData) { - console.log("Returning cached holder list."); - return cachedData; - } - - const allHoldersMap = new Map(); - let page = 1; - const limit = 1000; - let cursor; - //HELIOUS_API_KEY needs to be added - const url = `https://mainnet.helius-rpc.com/?api-key=${settings.HELIUS_API_KEY || ""}`; - console.log({ url }); - - try { - while (true) { - const params = { - limit: limit, - displayOptions: {}, - mint: this.tokenAddress, - cursor: cursor, - }; - if (cursor != undefined) { - params.cursor = cursor; - } - console.log(`Fetching holders - Page ${page}`); - if (page > 2) { - break; - } - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: "helius-test", - method: "getTokenAccounts", - params: params, - }), - }); - - const data = await response.json(); - - if ( - !data || - !data.result || - !data.result.token_accounts || - data.result.token_accounts.length === 0 - ) { - console.log( - `No more holders found. Total pages fetched: ${page - 1}` - ); - break; - } - - console.log( - `Processing ${data.result.token_accounts.length} holders from page ${page}` - ); - - data.result.token_accounts.forEach((account: any) => { - const owner = account.owner; - const balance = parseFloat(account.amount); - - if (allHoldersMap.has(owner)) { - allHoldersMap.set( - owner, - allHoldersMap.get(owner)! + balance - ); - } else { - allHoldersMap.set(owner, balance); - } - }); - cursor = data.result.cursor; - page++; - } - - const holders: HolderData[] = Array.from( - allHoldersMap.entries() - ).map(([address, balance]) => ({ - address, - balance: balance.toString(), - })); - - console.log(`Total unique holders fetched: ${holders.length}`); - - // Cache the result - this.setCachedData(cacheKey, holders); - - return holders; - } catch (error) { - console.error("Error fetching holder list from Helius:", error); - throw new Error("Failed to fetch holder list from Helius."); - } - } - - async filterHighValueHolders( - tradeData: TokenTradeData - ): Promise> { - const holdersData = await this.fetchHolderList(); - - const tokenPriceUsd = toBN(tradeData.price); - - const highValueHolders = holdersData - .filter((holder) => { - const balanceUsd = toBN(holder.balance).multipliedBy( - tokenPriceUsd - ); - return balanceUsd.isGreaterThan(5); - }) - .map((holder) => ({ - holderAddress: holder.address, - balanceUsd: toBN(holder.balance) - .multipliedBy(tokenPriceUsd) - .toFixed(2), - })); - - return highValueHolders; - } - - async checkRecentTrades(tradeData: TokenTradeData): Promise { - return toBN(tradeData.volume_24h_usd).isGreaterThan(0); - } - - async countHighSupplyHolders( - securityData: TokenSecurityData - ): Promise { - try { - const ownerBalance = toBN(securityData.ownerBalance); - const totalSupply = ownerBalance.plus(securityData.creatorBalance); - - const highSupplyHolders = await this.fetchHolderList(); - const highSupplyHoldersCount = highSupplyHolders.filter( - (holder) => { - const balance = toBN(holder.balance); - return balance.dividedBy(totalSupply).isGreaterThan(0.02); - } - ).length; - return highSupplyHoldersCount; - } catch (error) { - console.error("Error counting high supply holders:", error); - return 0; - } - } - - async getProcessedTokenData(): Promise { - try { - console.log( - `Fetching security data for token: ${this.tokenAddress}` - ); - const security = await this.fetchTokenSecurity(); - - const tokenCodex = await this.fetchTokenCodex(); - - console.log(`Fetching trade data for token: ${this.tokenAddress}`); - const tradeData = await this.fetchTokenTradeData(); - - console.log( - `Fetching DexScreener data for token: ${this.tokenAddress}` - ); - const dexData = await this.fetchDexScreenerData(); - - console.log( - `Analyzing holder distribution for token: ${this.tokenAddress}` - ); - const holderDistributionTrend = - await this.analyzeHolderDistribution(tradeData); - - console.log( - `Filtering high-value holders for token: ${this.tokenAddress}` - ); - const highValueHolders = - await this.filterHighValueHolders(tradeData); - - console.log( - `Checking recent trades for token: ${this.tokenAddress}` - ); - const recentTrades = await this.checkRecentTrades(tradeData); - - console.log( - `Counting high-supply holders for token: ${this.tokenAddress}` - ); - const highSupplyHoldersCount = - await this.countHighSupplyHolders(security); - - console.log( - `Determining DexScreener listing status for token: ${this.tokenAddress}` - ); - const isDexScreenerListed = dexData.pairs.length > 0; - const isDexScreenerPaid = dexData.pairs.some( - (pair) => pair.boosts && pair.boosts.active > 0 - ); - - const processedData: ProcessedTokenData = { - security, - tradeData, - holderDistributionTrend, - highValueHolders, - recentTrades, - highSupplyHoldersCount, - dexScreenerData: dexData, - isDexScreenerListed, - isDexScreenerPaid, - tokenCodex, - }; - - // console.log("Processed token data:", processedData); - return processedData; - } catch (error) { - console.error("Error processing token data:", error); - throw error; - } - } - - async shouldTradeToken(): Promise { - try { - const tokenData = await this.getProcessedTokenData(); - const { tradeData, security, dexScreenerData } = tokenData; - const { ownerBalance, creatorBalance } = security; - const { liquidity, marketCap } = dexScreenerData.pairs[0]; - const liquidityUsd = toBN(liquidity.usd); - const marketCapUsd = toBN(marketCap); - const totalSupply = toBN(ownerBalance).plus(creatorBalance); - const _ownerPercentage = toBN(ownerBalance).dividedBy(totalSupply); - const _creatorPercentage = - toBN(creatorBalance).dividedBy(totalSupply); - const top10HolderPercent = toBN(tradeData.volume_24h_usd).dividedBy( - totalSupply - ); - const priceChange24hPercent = toBN( - tradeData.price_change_24h_percent - ); - const priceChange12hPercent = toBN( - tradeData.price_change_12h_percent - ); - const uniqueWallet24h = tradeData.unique_wallet_24h; - const volume24hUsd = toBN(tradeData.volume_24h_usd); - const volume24hUsdThreshold = 1000; - const priceChange24hPercentThreshold = 10; - const priceChange12hPercentThreshold = 5; - const top10HolderPercentThreshold = 0.05; - const uniqueWallet24hThreshold = 100; - const isTop10Holder = top10HolderPercent.gte( - top10HolderPercentThreshold - ); - const isVolume24h = volume24hUsd.gte(volume24hUsdThreshold); - const isPriceChange24h = priceChange24hPercent.gte( - priceChange24hPercentThreshold - ); - const isPriceChange12h = priceChange12hPercent.gte( - priceChange12hPercentThreshold - ); - const isUniqueWallet24h = - uniqueWallet24h >= uniqueWallet24hThreshold; - const isLiquidityTooLow = liquidityUsd.lt(1000); - const isMarketCapTooLow = marketCapUsd.lt(100000); - return ( - isTop10Holder || - isVolume24h || - isPriceChange24h || - isPriceChange12h || - isUniqueWallet24h || - isLiquidityTooLow || - isMarketCapTooLow - ); - } catch (error) { - console.error("Error processing token data:", error); - throw error; - } - } - - formatTokenData(data: ProcessedTokenData): string { - let output = `**Token Security and Trade Report**\n`; - output += `Token Address: ${this.tokenAddress}\n\n`; - - // Security Data - 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.tradeData.holder}\n`; - output += `- Unique Wallets (24h): ${data.tradeData.unique_wallet_24h}\n`; - output += `- Price Change (24h): ${data.tradeData.price_change_24h_percent}%\n`; - output += `- Price Change (12h): ${data.tradeData.price_change_12h_percent}%\n`; - output += `- Volume (24h USD): $${toBN(data.tradeData.volume_24h_usd).toFixed(2)}\n`; - output += `- Current Price: $${toBN(data.tradeData.price).toFixed(2)}\n\n`; - - // Holder Distribution Trend - output += `**Holder Distribution Trend:** ${data.holderDistributionTrend}\n\n`; - - // High-Value Holders - output += `**High-Value Holders (>$5 USD):**\n`; - if (data.highValueHolders.length === 0) { - output += `- No high-value holders found or data not available.\n`; - } else { - data.highValueHolders.forEach((holder) => { - output += `- ${holder.holderAddress}: $${holder.balanceUsd}\n`; - }); - } - output += `\n`; - - // Recent Trades - output += `**Recent Trades (Last 24h):** ${data.recentTrades ? "Yes" : "No"}\n\n`; - - // High-Supply Holders - output += `**Holders with >2% Supply:** ${data.highSupplyHoldersCount}\n\n`; - - // DexScreener Status - output += `**DexScreener Listing:** ${data.isDexScreenerListed ? "Yes" : "No"}\n`; - if (data.isDexScreenerListed) { - output += `- Listing Type: ${data.isDexScreenerPaid ? "Paid" : "Free"}\n`; - output += `- Number of DexPairs: ${data.dexScreenerData.pairs.length}\n\n`; - output += `**DexScreener Pairs:**\n`; - data.dexScreenerData.pairs.forEach((pair, index) => { - output += `\n**Pair ${index + 1}:**\n`; - output += `- DEX: ${pair.dexId}\n`; - output += `- URL: ${pair.url}\n`; - output += `- Price USD: $${toBN(pair.priceUsd).toFixed(6)}\n`; - output += `- Volume (24h USD): $${toBN(pair.volume.h24).toFixed(2)}\n`; - output += `- Boosts Active: ${pair.boosts && pair.boosts.active}\n`; - output += `- Liquidity USD: $${toBN(pair.liquidity.usd).toFixed(2)}\n`; - }); - } - output += `\n`; - - console.log("Formatted token data:", output); - return output; - } - - async getFormattedTokenReport(): Promise { - try { - console.log("Generating formatted token report..."); - const processedData = await this.getProcessedTokenData(); - return this.formatTokenData(processedData); - } catch (error) { - console.error("Error generating token report:", error); - return "Unable to fetch token information. Please try again later."; - } - } -} - -const tokenAddress = PROVIDER_CONFIG.TOKEN_ADDRESSES.Example; - -const connection = new Connection(PROVIDER_CONFIG.DEFAULT_RPC); -const tokenProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - _message: Memory, - _state?: State - ): Promise => { - try { - const { publicKey } = await getWalletKey(runtime, false); - - const walletProvider = new WalletProvider(connection, publicKey); - - const provider = new TokenProvider( - tokenAddress, - walletProvider, - runtime.cacheManager - ); - - return provider.getFormattedTokenReport(); - } catch (error) { - console.error("Error fetching token data:", error); - return "Unable to fetch token information. Please try again later."; - } - }, -}; - -export { tokenProvider }; diff --git a/packages/plugin-solana-agentkit/src/providers/tokenUtils.ts b/packages/plugin-solana-agentkit/src/providers/tokenUtils.ts deleted file mode 100644 index 034dddc299..0000000000 --- a/packages/plugin-solana-agentkit/src/providers/tokenUtils.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { getAccount, getAssociatedTokenAddress } from "@solana/spl-token"; -import { Connection, PublicKey } from "@solana/web3.js"; - -export async function getTokenPriceInSol(tokenSymbol: string): Promise { - const response = await fetch( - `https://price.jup.ag/v6/price?ids=${tokenSymbol}` - ); - const data = await response.json(); - return data.data[tokenSymbol].price; -} - -async function getTokenBalance( - connection: Connection, - walletPublicKey: PublicKey, - tokenMintAddress: PublicKey -): Promise { - const tokenAccountAddress = await getAssociatedTokenAddress( - tokenMintAddress, - walletPublicKey - ); - - try { - const tokenAccount = await getAccount(connection, tokenAccountAddress); - const tokenAmount = tokenAccount.amount as unknown as number; - return tokenAmount; - } catch (error) { - console.error( - `Error retrieving balance for token: ${tokenMintAddress.toBase58()}`, - error - ); - return 0; - } -} - -async function getTokenBalances( - connection: Connection, - walletPublicKey: PublicKey -): Promise<{ [tokenName: string]: number }> { - const tokenBalances: { [tokenName: string]: number } = {}; - - // Add the token mint addresses you want to retrieve balances for - const tokenMintAddresses = [ - new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), // USDC - new PublicKey("So11111111111111111111111111111111111111112"), // SOL - // Add more token mint addresses as needed - ]; - - for (const mintAddress of tokenMintAddresses) { - const tokenName = getTokenName(mintAddress); - const balance = await getTokenBalance( - connection, - walletPublicKey, - mintAddress - ); - tokenBalances[tokenName] = balance; - } - - return tokenBalances; -} - -function getTokenName(mintAddress: PublicKey): string { - // Implement a mapping of mint addresses to token names - const tokenNameMap: { [mintAddress: string]: string } = { - EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v: "USDC", - So11111111111111111111111111111111111111112: "SOL", - // Add more token mint addresses and their corresponding names - }; - - return tokenNameMap[mintAddress.toBase58()] || "Unknown Token"; -} - -export { getTokenBalance, getTokenBalances }; diff --git a/packages/plugin-solana-agentkit/src/providers/trustScoreProvider.ts b/packages/plugin-solana-agentkit/src/providers/trustScoreProvider.ts deleted file mode 100644 index 931cd9b44d..0000000000 --- a/packages/plugin-solana-agentkit/src/providers/trustScoreProvider.ts +++ /dev/null @@ -1,740 +0,0 @@ -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"; -import { - TrustScoreDatabase, - RecommenderMetrics, - TokenPerformance, - TradePerformance, - TokenRecommendation, -} from "@elizaos/plugin-trustdb"; -import { settings } from "@elizaos/core"; -import { IAgentRuntime, Memory, Provider, State } from "@elizaos/core"; -import { v4 as uuidv4 } from "uuid"; - -const Wallet = settings.MAIN_WALLET_ADDRESS; -interface TradeData { - buy_amount: number; - is_simulation: boolean; -} -interface sellDetails { - sell_amount: number; - sell_recommender_id: string | null; -} -interface _RecommendationGroup { - recommendation: any; - trustScore: number; -} - -interface RecommenderData { - recommenderId: string; - trustScore: number; - riskScore: number; - consistencyScore: number; - recommenderMetrics: RecommenderMetrics; -} - -interface TokenRecommendationSummary { - tokenAddress: string; - averageTrustScore: number; - averageRiskScore: number; - averageConsistencyScore: number; - recommenders: RecommenderData[]; -} -export class TrustScoreManager { - private tokenProvider: TokenProvider; - private trustScoreDb: TrustScoreDatabase; - private simulationSellingService: SimulationSellingService; - private connection: Connection; - private baseMint: PublicKey; - private DECAY_RATE = 0.95; - private MAX_DECAY_DAYS = 30; - private backend; - private backendToken; - constructor( - runtime: IAgentRuntime, - tokenProvider: TokenProvider, - trustScoreDb: TrustScoreDatabase - ) { - this.tokenProvider = tokenProvider; - this.trustScoreDb = trustScoreDb; - this.connection = new Connection(runtime.getSetting("RPC_URL")); - this.baseMint = new PublicKey( - runtime.getSetting("BASE_MINT") || - "So11111111111111111111111111111111111111112" - ); - this.backend = runtime.getSetting("BACKEND_URL"); - this.backendToken = runtime.getSetting("BACKEND_TOKEN"); - this.simulationSellingService = new SimulationSellingService( - runtime, - this.trustScoreDb - ); - } - - //getRecommenederBalance - async getRecommenederBalance(recommenderWallet: string): Promise { - try { - const tokenAta = await getAssociatedTokenAddress( - new PublicKey(recommenderWallet), - this.baseMint - ); - const tokenBalInfo = - await this.connection.getTokenAccountBalance(tokenAta); - const tokenBalance = tokenBalInfo.value.amount; - const balance = parseFloat(tokenBalance); - return balance; - } catch (error) { - console.error("Error fetching balance", error); - return 0; - } - } - - /** - * Generates and saves trust score based on processed token data and user recommendations. - * @param tokenAddress The address of the token to analyze. - * @param recommenderId The UUID of the recommender. - * @returns An object containing TokenPerformance and RecommenderMetrics. - */ - async generateTrustScore( - tokenAddress: string, - recommenderId: string, - recommenderWallet: string - ): Promise<{ - tokenPerformance: TokenPerformance; - recommenderMetrics: RecommenderMetrics; - }> { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - console.log(`Fetched processed token data for token: ${tokenAddress}`); - - const recommenderMetrics = - await this.trustScoreDb.getRecommenderMetrics(recommenderId); - - const isRapidDump = await this.isRapidDump(tokenAddress); - const sustainedGrowth = await this.sustainedGrowth(tokenAddress); - const suspiciousVolume = await this.suspiciousVolume(tokenAddress); - const balance = await this.getRecommenederBalance(recommenderWallet); - const virtualConfidence = balance / 1000000; // TODO: create formula to calculate virtual confidence based on user balance - const lastActive = recommenderMetrics.lastActiveDate; - const now = new Date(); - const inactiveDays = Math.floor( - (now.getTime() - lastActive.getTime()) / (1000 * 60 * 60 * 24) - ); - const decayFactor = Math.pow( - this.DECAY_RATE, - Math.min(inactiveDays, this.MAX_DECAY_DAYS) - ); - const decayedScore = recommenderMetrics.trustScore * decayFactor; - const validationTrustScore = - this.trustScoreDb.calculateValidationTrust(tokenAddress); - - return { - tokenPerformance: { - tokenAddress: - processedData.dexScreenerData.pairs[0]?.baseToken.address || - "", - priceChange24h: - processedData.tradeData.price_change_24h_percent, - volumeChange24h: processedData.tradeData.volume_24h, - trade_24h_change: - processedData.tradeData.trade_24h_change_percent, - liquidity: - processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, - liquidityChange24h: 0, - holderChange24h: - processedData.tradeData.unique_wallet_24h_change_percent, - rugPull: false, - isScam: processedData.tokenCodex.isScam, - marketCapChange24h: 0, - sustainedGrowth: sustainedGrowth, - rapidDump: isRapidDump, - suspiciousVolume: suspiciousVolume, - validationTrust: validationTrustScore, - balance: balance, - initialMarketCap: - processedData.dexScreenerData.pairs[0]?.marketCap || 0, - lastUpdated: new Date(), - symbol: "", - }, - recommenderMetrics: { - recommenderId: recommenderId, - trustScore: recommenderMetrics.trustScore, - totalRecommendations: recommenderMetrics.totalRecommendations, - successfulRecs: recommenderMetrics.successfulRecs, - avgTokenPerformance: recommenderMetrics.avgTokenPerformance, - riskScore: recommenderMetrics.riskScore, - consistencyScore: recommenderMetrics.consistencyScore, - virtualConfidence: virtualConfidence, - lastActiveDate: now, - trustDecay: decayedScore, - lastUpdated: new Date(), - }, - }; - } - - async updateRecommenderMetrics( - recommenderId: string, - tokenPerformance: TokenPerformance, - recommenderWallet: string - ): Promise { - const recommenderMetrics = - await this.trustScoreDb.getRecommenderMetrics(recommenderId); - - const totalRecommendations = - recommenderMetrics.totalRecommendations + 1; - const successfulRecs = tokenPerformance.rugPull - ? recommenderMetrics.successfulRecs - : recommenderMetrics.successfulRecs + 1; - const avgTokenPerformance = - (recommenderMetrics.avgTokenPerformance * - recommenderMetrics.totalRecommendations + - tokenPerformance.priceChange24h) / - totalRecommendations; - - const overallTrustScore = this.calculateTrustScore( - tokenPerformance, - recommenderMetrics - ); - const riskScore = this.calculateOverallRiskScore( - tokenPerformance, - recommenderMetrics - ); - const consistencyScore = this.calculateConsistencyScore( - tokenPerformance, - recommenderMetrics - ); - - const balance = await this.getRecommenederBalance(recommenderWallet); - const virtualConfidence = balance / 1000000; // TODO: create formula to calculate virtual confidence based on user balance - const lastActive = recommenderMetrics.lastActiveDate; - const now = new Date(); - const inactiveDays = Math.floor( - (now.getTime() - lastActive.getTime()) / (1000 * 60 * 60 * 24) - ); - const decayFactor = Math.pow( - this.DECAY_RATE, - Math.min(inactiveDays, this.MAX_DECAY_DAYS) - ); - const decayedScore = recommenderMetrics.trustScore * decayFactor; - - const newRecommenderMetrics: RecommenderMetrics = { - recommenderId: recommenderId, - trustScore: overallTrustScore, - totalRecommendations: totalRecommendations, - successfulRecs: successfulRecs, - avgTokenPerformance: avgTokenPerformance, - riskScore: riskScore, - consistencyScore: consistencyScore, - virtualConfidence: virtualConfidence, - lastActiveDate: new Date(), - trustDecay: decayedScore, - lastUpdated: new Date(), - }; - - await this.trustScoreDb.updateRecommenderMetrics(newRecommenderMetrics); - } - - calculateTrustScore( - tokenPerformance: TokenPerformance, - recommenderMetrics: RecommenderMetrics - ): number { - const riskScore = this.calculateRiskScore(tokenPerformance); - const consistencyScore = this.calculateConsistencyScore( - tokenPerformance, - recommenderMetrics - ); - - return (riskScore + consistencyScore) / 2; - } - - calculateOverallRiskScore( - tokenPerformance: TokenPerformance, - recommenderMetrics: RecommenderMetrics - ) { - const riskScore = this.calculateRiskScore(tokenPerformance); - const consistencyScore = this.calculateConsistencyScore( - tokenPerformance, - recommenderMetrics - ); - - return (riskScore + consistencyScore) / 2; - } - - calculateRiskScore(tokenPerformance: TokenPerformance): number { - let riskScore = 0; - if (tokenPerformance.rugPull) { - riskScore += 10; - } - if (tokenPerformance.isScam) { - riskScore += 10; - } - if (tokenPerformance.rapidDump) { - riskScore += 5; - } - if (tokenPerformance.suspiciousVolume) { - riskScore += 5; - } - return riskScore; - } - - calculateConsistencyScore( - tokenPerformance: TokenPerformance, - recommenderMetrics: RecommenderMetrics - ): number { - const avgTokenPerformance = recommenderMetrics.avgTokenPerformance; - const priceChange24h = tokenPerformance.priceChange24h; - - return Math.abs(priceChange24h - avgTokenPerformance); - } - - async suspiciousVolume(tokenAddress: string): Promise { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - const unique_wallet_24h = processedData.tradeData.unique_wallet_24h; - const volume_24h = processedData.tradeData.volume_24h; - const suspiciousVolume = unique_wallet_24h / volume_24h > 0.5; - console.log(`Fetched processed token data for token: ${tokenAddress}`); - return suspiciousVolume; - } - - async sustainedGrowth(tokenAddress: string): Promise { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - console.log(`Fetched processed token data for token: ${tokenAddress}`); - - return processedData.tradeData.volume_24h_change_percent > 50; - } - - async isRapidDump(tokenAddress: string): Promise { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - console.log(`Fetched processed token data for token: ${tokenAddress}`); - - return processedData.tradeData.trade_24h_change_percent < -50; - } - - async checkTrustScore(tokenAddress: string): Promise { - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - console.log(`Fetched processed token data for token: ${tokenAddress}`); - - return { - ownerBalance: processedData.security.ownerBalance, - creatorBalance: processedData.security.creatorBalance, - ownerPercentage: processedData.security.ownerPercentage, - creatorPercentage: processedData.security.creatorPercentage, - top10HolderBalance: processedData.security.top10HolderBalance, - top10HolderPercent: processedData.security.top10HolderPercent, - }; - } - - /** - * Creates a TradePerformance object based on token data and recommender. - * @param tokenAddress The address of the token. - * @param recommenderId The UUID of the recommender. - * @param data ProcessedTokenData. - * @returns TradePerformance object. - */ - async createTradePerformance( - runtime: IAgentRuntime, - tokenAddress: string, - recommenderId: string, - data: TradeData - ): Promise { - const recommender = - await this.trustScoreDb.getOrCreateRecommenderWithTelegramId( - recommenderId - ); - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - const wallet = new WalletProvider( - this.connection, - new PublicKey(Wallet!) - ); - - let tokensBalance = 0; - const prices = await wallet.fetchPrices(runtime); - const solPrice = prices.solana.usd; - const buySol = data.buy_amount / parseFloat(solPrice); - const buy_value_usd = data.buy_amount * processedData.tradeData.price; - const token = await this.tokenProvider.fetchTokenTradeData(); - const tokenCodex = await this.tokenProvider.fetchTokenCodex(); - const tokenPrice = token.price; - tokensBalance = buy_value_usd / tokenPrice; - - const creationData = { - token_address: tokenAddress, - recommender_id: recommender.id, - buy_price: processedData.tradeData.price, - sell_price: 0, - buy_timeStamp: new Date().toISOString(), - sell_timeStamp: "", - buy_amount: data.buy_amount, - sell_amount: 0, - buy_sol: buySol, - received_sol: 0, - buy_value_usd: buy_value_usd, - sell_value_usd: 0, - profit_usd: 0, - profit_percent: 0, - buy_market_cap: - processedData.dexScreenerData.pairs[0]?.marketCap || 0, - sell_market_cap: 0, - market_cap_change: 0, - buy_liquidity: - processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, - sell_liquidity: 0, - liquidity_change: 0, - last_updated: new Date().toISOString(), - rapidDump: false, - }; - this.trustScoreDb.addTradePerformance(creationData, data.is_simulation); - // generate unique uuid for each TokenRecommendation - const tokenUUId = uuidv4(); - const tokenRecommendation: TokenRecommendation = { - id: tokenUUId, - recommenderId: recommenderId, - tokenAddress: tokenAddress, - timestamp: new Date(), - initialMarketCap: - processedData.dexScreenerData.pairs[0]?.marketCap || 0, - initialLiquidity: - processedData.dexScreenerData.pairs[0]?.liquidity?.usd || 0, - initialPrice: processedData.tradeData.price, - }; - this.trustScoreDb.addTokenRecommendation(tokenRecommendation); - - this.trustScoreDb.upsertTokenPerformance({ - tokenAddress: tokenAddress, - symbol: processedData.tokenCodex.symbol, - priceChange24h: processedData.tradeData.price_change_24h_percent, - volumeChange24h: processedData.tradeData.volume_24h, - trade_24h_change: processedData.tradeData.trade_24h_change_percent, - liquidity: - processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0, - liquidityChange24h: 0, - holderChange24h: - processedData.tradeData.unique_wallet_24h_change_percent, - rugPull: false, - isScam: tokenCodex.isScam, - marketCapChange24h: 0, - sustainedGrowth: false, - rapidDump: false, - suspiciousVolume: false, - validationTrust: 0, - balance: tokensBalance, - initialMarketCap: - processedData.dexScreenerData.pairs[0]?.marketCap || 0, - lastUpdated: new Date(), - }); - - if (data.is_simulation) { - // If the trade is a simulation update the balance - this.trustScoreDb.updateTokenBalance(tokenAddress, tokensBalance); - // generate some random hash for simulations - const hash = Math.random().toString(36).substring(7); - const transaction = { - tokenAddress: tokenAddress, - type: "buy" as "buy" | "sell", - transactionHash: hash, - amount: data.buy_amount, - price: processedData.tradeData.price, - isSimulation: true, - timestamp: new Date().toISOString(), - }; - this.trustScoreDb.addTransaction(transaction); - } - this.simulationSellingService.processTokenPerformance( - tokenAddress, - recommenderId - ); - // api call to update trade performance - this.createTradeInBe(tokenAddress, recommenderId, data); - return creationData; - } - - async delay(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - - async createTradeInBe( - tokenAddress: string, - recommenderId: string, - data: TradeData, - retries = 3, - delayMs = 2000 - ) { - for (let attempt = 1; attempt <= retries; attempt++) { - try { - await fetch( - `${this.backend}/api/updaters/createTradePerformance`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${this.backendToken}`, - }, - body: JSON.stringify({ - tokenAddress: tokenAddress, - tradeData: data, - recommenderId: recommenderId, - }), - } - ); - // If the request is successful, exit the loop - return; - } catch (error) { - console.error( - `Attempt ${attempt} failed: Error creating trade in backend`, - error - ); - if (attempt < retries) { - console.log(`Retrying in ${delayMs} ms...`); - await this.delay(delayMs); // Wait for the specified delay before retrying - } else { - console.error("All attempts failed."); - } - } - } - } - - /** - * Updates a trade with sell details. - * @param tokenAddress The address of the token. - * @param recommenderId The UUID of the recommender. - * @param buyTimeStamp The timestamp when the buy occurred. - * @param sellDetails An object containing sell-related details. - * @param isSimulation Whether the trade is a simulation. If true, updates in simulation_trade; otherwise, in trade. - * @returns boolean indicating success. - */ - - async updateSellDetails( - runtime: IAgentRuntime, - tokenAddress: string, - recommenderId: string, - sellTimeStamp: string, - sellDetails: sellDetails, - isSimulation: boolean - ) { - const recommender = - await this.trustScoreDb.getOrCreateRecommenderWithTelegramId( - recommenderId - ); - const processedData: ProcessedTokenData = - await this.tokenProvider.getProcessedTokenData(); - const wallet = new WalletProvider( - this.connection, - new PublicKey(Wallet!) - ); - const prices = await wallet.fetchPrices(runtime); - const solPrice = prices.solana.usd; - const sellSol = sellDetails.sell_amount / parseFloat(solPrice); - const sell_value_usd = - sellDetails.sell_amount * processedData.tradeData.price; - const trade = await this.trustScoreDb.getLatestTradePerformance( - tokenAddress, - recommender.id, - isSimulation - ); - const buyTimeStamp = trade.buy_timeStamp; - const marketCap = - processedData.dexScreenerData.pairs[0]?.marketCap || 0; - const liquidity = - processedData.dexScreenerData.pairs[0]?.liquidity.usd || 0; - const sell_price = processedData.tradeData.price; - const profit_usd = sell_value_usd - trade.buy_value_usd; - const profit_percent = (profit_usd / trade.buy_value_usd) * 100; - - const market_cap_change = marketCap - trade.buy_market_cap; - const liquidity_change = liquidity - trade.buy_liquidity; - - const isRapidDump = await this.isRapidDump(tokenAddress); - - const sellDetailsData = { - sell_price: sell_price, - sell_timeStamp: sellTimeStamp, - sell_amount: sellDetails.sell_amount, - received_sol: sellSol, - sell_value_usd: sell_value_usd, - profit_usd: profit_usd, - profit_percent: profit_percent, - sell_market_cap: marketCap, - market_cap_change: market_cap_change, - sell_liquidity: liquidity, - liquidity_change: liquidity_change, - rapidDump: isRapidDump, - sell_recommender_id: sellDetails.sell_recommender_id || null, - }; - this.trustScoreDb.updateTradePerformanceOnSell( - tokenAddress, - recommender.id, - buyTimeStamp, - sellDetailsData, - isSimulation - ); - if (isSimulation) { - // If the trade is a simulation update the balance - const oldBalance = this.trustScoreDb.getTokenBalance(tokenAddress); - const tokenBalance = oldBalance - sellDetails.sell_amount; - this.trustScoreDb.updateTokenBalance(tokenAddress, tokenBalance); - // generate some random hash for simulations - const hash = Math.random().toString(36).substring(7); - const transaction = { - tokenAddress: tokenAddress, - type: "sell" as "buy" | "sell", - transactionHash: hash, - amount: sellDetails.sell_amount, - price: processedData.tradeData.price, - isSimulation: true, - timestamp: new Date().toISOString(), - }; - this.trustScoreDb.addTransaction(transaction); - } - - return sellDetailsData; - } - - // get all recommendations - async getRecommendations( - startDate: Date, - endDate: Date - ): Promise> { - const recommendations = this.trustScoreDb.getRecommendationsByDateRange( - startDate, - endDate - ); - - // Group recommendations by tokenAddress - const groupedRecommendations = recommendations.reduce( - (acc, recommendation) => { - const { tokenAddress } = recommendation; - if (!acc[tokenAddress]) acc[tokenAddress] = []; - acc[tokenAddress].push(recommendation); - return acc; - }, - {} as Record> - ); - - const result = Object.keys(groupedRecommendations).map( - (tokenAddress) => { - const tokenRecommendations = - groupedRecommendations[tokenAddress]; - - // Initialize variables to compute averages - let totalTrustScore = 0; - let totalRiskScore = 0; - let totalConsistencyScore = 0; - const recommenderData = []; - - tokenRecommendations.forEach((recommendation) => { - const tokenPerformance = - this.trustScoreDb.getTokenPerformance( - recommendation.tokenAddress - ); - const recommenderMetrics = - this.trustScoreDb.getRecommenderMetrics( - recommendation.recommenderId - ); - - const trustScore = this.calculateTrustScore( - tokenPerformance, - recommenderMetrics - ); - const consistencyScore = this.calculateConsistencyScore( - tokenPerformance, - recommenderMetrics - ); - const riskScore = this.calculateRiskScore(tokenPerformance); - - // Accumulate scores for averaging - totalTrustScore += trustScore; - totalRiskScore += riskScore; - totalConsistencyScore += consistencyScore; - - recommenderData.push({ - recommenderId: recommendation.recommenderId, - trustScore, - riskScore, - consistencyScore, - recommenderMetrics, - }); - }); - - // Calculate averages for this token - const averageTrustScore = - totalTrustScore / tokenRecommendations.length; - const averageRiskScore = - totalRiskScore / tokenRecommendations.length; - const averageConsistencyScore = - totalConsistencyScore / tokenRecommendations.length; - - return { - tokenAddress, - averageTrustScore, - averageRiskScore, - averageConsistencyScore, - recommenders: recommenderData, - }; - } - ); - - // Sort recommendations by the highest average trust score - result.sort((a, b) => b.averageTrustScore - a.averageTrustScore); - - return result; - } -} - -export const trustScoreProvider: Provider = { - async get( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise { - try { - const trustScoreDb = new TrustScoreDatabase( - runtime.databaseAdapter.db - ); - - // Get the user ID from the message - const userId = message.userId; - - if (!userId) { - console.error("User ID is missing from the message"); - return ""; - } - - // Get the recommender metrics for the user - const recommenderMetrics = - await trustScoreDb.getRecommenderMetrics(userId); - - if (!recommenderMetrics) { - console.error("No recommender metrics found for user:", userId); - return ""; - } - - // Compute the trust score - const trustScore = recommenderMetrics.trustScore; - - const user = await runtime.databaseAdapter.getAccountById(userId); - - // Format the trust score string - const trustScoreString = `${user.name}'s trust score: ${trustScore.toFixed(2)}`; - - return trustScoreString; - } catch (error) { - console.error("Error in trust score provider:", error.message); - return `Failed to fetch trust score: ${error instanceof Error ? error.message : "Unknown error"}`; - } - }, -}; diff --git a/packages/plugin-solana-agentkit/src/providers/wallet.ts b/packages/plugin-solana-agentkit/src/providers/wallet.ts deleted file mode 100644 index 7e3c55580b..0000000000 --- a/packages/plugin-solana-agentkit/src/providers/wallet.ts +++ /dev/null @@ -1,391 +0,0 @@ -import { IAgentRuntime, Memory, Provider, State } from "@elizaos/core"; -import { Connection, PublicKey } from "@solana/web3.js"; -import BigNumber from "bignumber.js"; -import NodeCache from "node-cache"; -import { getWalletKey } from "../keypairUtils"; - -// Provider configuration -const PROVIDER_CONFIG = { - BIRDEYE_API: "https://public-api.birdeye.so", - MAX_RETRIES: 3, - RETRY_DELAY: 2000, - DEFAULT_RPC: "https://api.mainnet-beta.solana.com", - GRAPHQL_ENDPOINT: "https://graph.codex.io/graphql", - TOKEN_ADDRESSES: { - SOL: "So11111111111111111111111111111111111111112", - BTC: "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh", - ETH: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", - }, -}; - -export interface Item { - name: string; - address: string; - symbol: string; - decimals: number; - balance: string; - uiAmount: string; - priceUsd: string; - valueUsd: string; - valueSol?: string; -} - -interface WalletPortfolio { - totalUsd: string; - totalSol?: string; - items: Array; -} - -interface _BirdEyePriceData { - data: { - [key: string]: { - price: number; - priceChange24h: number; - }; - }; -} - -interface Prices { - solana: { usd: string }; - bitcoin: { usd: string }; - ethereum: { usd: string }; -} - -export class WalletProvider { - private cache: NodeCache; - - constructor( - private connection: Connection, - private walletPublicKey: PublicKey - ) { - this.cache = new NodeCache({ stdTTL: 300 }); // Cache TTL set to 5 minutes - } - - private async fetchWithRetry( - runtime, - url: string, - options: RequestInit = {} - ): Promise { - let lastError: Error; - - for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) { - try { - const response = await fetch(url, { - ...options, - headers: { - Accept: "application/json", - "x-chain": "solana", - "X-API-KEY": - runtime.getSetting("BIRDEYE_API_KEY", "") || "", - ...options.headers, - }, - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error( - `HTTP error! status: ${response.status}, message: ${errorText}` - ); - } - - const data = await response.json(); - return data; - } catch (error) { - console.error(`Attempt ${i + 1} failed:`, error); - lastError = error; - if (i < PROVIDER_CONFIG.MAX_RETRIES - 1) { - const delay = PROVIDER_CONFIG.RETRY_DELAY * Math.pow(2, i); - await new Promise((resolve) => setTimeout(resolve, delay)); - continue; - } - } - } - - console.error( - "All attempts failed. Throwing the last error:", - lastError - ); - throw lastError; - } - - async fetchPortfolioValue(runtime): Promise { - try { - const cacheKey = `portfolio-${this.walletPublicKey.toBase58()}`; - const cachedValue = this.cache.get(cacheKey); - - if (cachedValue) { - console.log("Cache hit for fetchPortfolioValue"); - return cachedValue; - } - console.log("Cache miss for fetchPortfolioValue"); - - const walletData = await this.fetchWithRetry( - runtime, - `${PROVIDER_CONFIG.BIRDEYE_API}/v1/wallet/token_list?wallet=${this.walletPublicKey.toBase58()}` - ); - - if (!walletData?.success || !walletData?.data) { - console.error("No portfolio data available", walletData); - throw new Error("No portfolio data available"); - } - - const data = walletData.data; - const totalUsd = new BigNumber(data.totalUsd.toString()); - const prices = await this.fetchPrices(runtime); - const solPriceInUSD = new BigNumber(prices.solana.usd.toString()); - - const items = data.items.map((item: any) => ({ - ...item, - valueSol: new BigNumber(item.valueUsd || 0) - .div(solPriceInUSD) - .toFixed(6), - name: item.name || "Unknown", - symbol: item.symbol || "Unknown", - priceUsd: item.priceUsd || "0", - valueUsd: item.valueUsd || "0", - })); - - const totalSol = totalUsd.div(solPriceInUSD); - const portfolio = { - totalUsd: totalUsd.toString(), - totalSol: totalSol.toFixed(6), - items: items.sort((a, b) => - new BigNumber(b.valueUsd) - .minus(new BigNumber(a.valueUsd)) - .toNumber() - ), - }; - this.cache.set(cacheKey, portfolio); - return portfolio; - } catch (error) { - console.error("Error fetching portfolio:", error); - throw error; - } - } - - async fetchPortfolioValueCodex(runtime): Promise { - try { - const cacheKey = `portfolio-${this.walletPublicKey.toBase58()}`; - const cachedValue = await this.cache.get(cacheKey); - - if (cachedValue) { - console.log("Cache hit for fetchPortfolioValue"); - return cachedValue; - } - console.log("Cache miss for fetchPortfolioValue"); - - const query = ` - query Balances($walletId: String!, $cursor: String) { - balances(input: { walletId: $walletId, cursor: $cursor }) { - cursor - items { - walletId - tokenId - balance - shiftedBalance - } - } - } - `; - - const variables = { - walletId: `${this.walletPublicKey.toBase58()}:${1399811149}`, - cursor: null, - }; - - const response = await fetch(PROVIDER_CONFIG.GRAPHQL_ENDPOINT, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: - runtime.getSetting("CODEX_API_KEY", "") || "", - }, - body: JSON.stringify({ - query, - variables, - }), - }).then((res) => res.json()); - - const data = response.data?.data?.balances?.items; - - if (!data || data.length === 0) { - console.error("No portfolio data available", data); - throw new Error("No portfolio data available"); - } - - // Fetch token prices - const prices = await this.fetchPrices(runtime); - const solPriceInUSD = new BigNumber(prices.solana.usd.toString()); - - // Reformat items - const items: Item[] = data.map((item: any) => { - return { - name: "Unknown", - address: item.tokenId.split(":")[0], - symbol: item.tokenId.split(":")[0], - decimals: 6, - balance: item.balance, - uiAmount: item.shiftedBalance.toString(), - priceUsd: "", - valueUsd: "", - valueSol: "", - }; - }); - - // Calculate total portfolio value - const totalUsd = items.reduce( - (sum, item) => sum.plus(new BigNumber(item.valueUsd)), - new BigNumber(0) - ); - - const totalSol = totalUsd.div(solPriceInUSD); - - const portfolio: WalletPortfolio = { - totalUsd: totalUsd.toFixed(6), - totalSol: totalSol.toFixed(6), - items: items.sort((a, b) => - new BigNumber(b.valueUsd) - .minus(new BigNumber(a.valueUsd)) - .toNumber() - ), - }; - - // Cache the portfolio for future requests - await this.cache.set(cacheKey, portfolio, 60 * 1000); // Cache for 1 minute - - return portfolio; - } catch (error) { - console.error("Error fetching portfolio:", error); - throw error; - } - } - - async fetchPrices(runtime): Promise { - try { - const cacheKey = "prices"; - const cachedValue = this.cache.get(cacheKey); - - if (cachedValue) { - console.log("Cache hit for fetchPrices"); - return cachedValue; - } - console.log("Cache miss for fetchPrices"); - - const { SOL, BTC, ETH } = PROVIDER_CONFIG.TOKEN_ADDRESSES; - const tokens = [SOL, BTC, ETH]; - const prices: Prices = { - solana: { usd: "0" }, - bitcoin: { usd: "0" }, - ethereum: { usd: "0" }, - }; - - for (const token of tokens) { - const response = await this.fetchWithRetry( - runtime, - `${PROVIDER_CONFIG.BIRDEYE_API}/defi/price?address=${token}`, - { - headers: { - "x-chain": "solana", - }, - } - ); - - if (response?.data?.value) { - const price = response.data.value.toString(); - prices[ - token === SOL - ? "solana" - : token === BTC - ? "bitcoin" - : "ethereum" - ].usd = price; - } else { - console.warn(`No price data available for token: ${token}`); - } - } - - this.cache.set(cacheKey, prices); - return prices; - } catch (error) { - console.error("Error fetching prices:", error); - throw error; - } - } - - formatPortfolio( - runtime, - portfolio: WalletPortfolio, - prices: Prices - ): string { - let output = `${runtime.character.description}\n`; - output += `Wallet Address: ${this.walletPublicKey.toBase58()}\n\n`; - - const totalUsdFormatted = new BigNumber(portfolio.totalUsd).toFixed(2); - const totalSolFormatted = portfolio.totalSol; - - output += `Total Value: $${totalUsdFormatted} (${totalSolFormatted} SOL)\n\n`; - output += "Token Balances:\n"; - - const nonZeroItems = portfolio.items.filter((item) => - new BigNumber(item.uiAmount).isGreaterThan(0) - ); - - if (nonZeroItems.length === 0) { - output += "No tokens found with non-zero balance\n"; - } else { - for (const item of nonZeroItems) { - const valueUsd = new BigNumber(item.valueUsd).toFixed(2); - output += `${item.name} (${item.symbol}): ${new BigNumber( - item.uiAmount - ).toFixed(6)} ($${valueUsd} | ${item.valueSol} SOL)\n`; - } - } - - output += "\nMarket Prices:\n"; - output += `SOL: $${new BigNumber(prices.solana.usd).toFixed(2)}\n`; - output += `BTC: $${new BigNumber(prices.bitcoin.usd).toFixed(2)}\n`; - output += `ETH: $${new BigNumber(prices.ethereum.usd).toFixed(2)}\n`; - - return output; - } - - async getFormattedPortfolio(runtime): Promise { - try { - const [portfolio, prices] = await Promise.all([ - this.fetchPortfolioValue(runtime), - this.fetchPrices(runtime), - ]); - - return this.formatPortfolio(runtime, portfolio, prices); - } catch (error) { - console.error("Error generating portfolio report:", error); - return "Unable to fetch wallet information. Please try again later."; - } - } -} - -const walletProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - _message: Memory, - _state?: State - ): Promise => { - try { - const { publicKey } = await getWalletKey(runtime, false); - - const connection = new Connection( - runtime.getSetting("RPC_URL") || PROVIDER_CONFIG.DEFAULT_RPC - ); - - const provider = new WalletProvider(connection, publicKey); - - return await provider.getFormattedPortfolio(runtime); - } catch (error) { - console.error("Error in wallet provider:", error); - return null; - } - }, -}; - -// Module exports -export { walletProvider }; From 5ae88bcfcdf3e3e67fad9b5ef5727487bda231e4 Mon Sep 17 00:00:00 2001 From: xiaohuo Date: Sun, 22 Dec 2024 20:33:28 +0800 Subject: [PATCH 3/7] feat: handle token deploy --- .../src/actions/createToken.ts | 98 ++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/packages/plugin-solana-agentkit/src/actions/createToken.ts b/packages/plugin-solana-agentkit/src/actions/createToken.ts index 74669e52fb..a225cc1bbb 100644 --- a/packages/plugin-solana-agentkit/src/actions/createToken.ts +++ b/packages/plugin-solana-agentkit/src/actions/createToken.ts @@ -1,7 +1,9 @@ import { ActionExample, + composeContext, Content, elizaLogger, + generateObjectDeprecated, HandlerCallback, IAgentRuntime, Memory, @@ -10,6 +12,8 @@ import { type Action, } from "@elizaos/core"; +import { SolanaAgentKit } from "solana-agent-kit"; + export interface CreateTokenContent extends Content { name: string; uri: string; @@ -32,6 +36,30 @@ function isCreateTokenContent( ); } +const createTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. + +Example response: +\`\`\`json +{ + "name": "Example Token", + "symbol": "EXMPL", + "uri": "https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/CompressedCoil/image.png", + "decimals": 18, + "initialSupply": 1000000, +} +\`\`\` + +{{recentMessages}} + +Given the recent messages, extract the following information about the requested token transfer: +- Token name +- Token symbol +- Token uri +- Token decimals +- Token initialSupply + +Respond with a JSON markdown block containing only the extracted values.`; + export default { name: "CREATE_TOKEN", similes: ["DEPLOY_TOKEN"], @@ -45,7 +73,75 @@ export default { callback?: HandlerCallback ): Promise => { elizaLogger.log("Starting CREATE_TOKEN handler..."); - return true; + // Initialize or update state + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + // Compose transfer context + const transferContext = composeContext({ + state, + template: createTemplate, + }); + + // Generate transfer content + const content = await generateObjectDeprecated({ + runtime, + context: transferContext, + modelClass: ModelClass.LARGE, + }); + + // Validate transfer content + if (!isCreateTokenContent(runtime, content)) { + elizaLogger.error("Invalid content for CREATE_TOKEN action."); + if (callback) { + callback({ + text: "Unable to process create token request. Invalid content provided.", + content: { error: "Invalid creat token content" }, + }); + } + return false; + } + + elizaLogger.log("Init solana agent kit..."); + const solanaPrivatekey = runtime.getSetting("SOLANA_PRIVATE_KEY"); + const rpc = runtime.getSetting("RPC_URL"); + const openAIKey = runtime.getSetting("OPENAI_API_KEY"); + const solanaAgentKit = new SolanaAgentKit( + solanaPrivatekey, + rpc, + openAIKey + ); + try { + const deployedAddress = solanaAgentKit.deployToken( + content.name, + content.uri, + content.symbol, + content.decimals, + content.initialSupply + ); + elizaLogger.log("Create successful: ", deployedAddress); + if (callback) { + callback({ + text: `Successfully create token ${content.name}`, + content: { + success: true, + }, + }); + } + return true; + } catch (error) { + if (callback) { + elizaLogger.error("Error during create token: ", error); + callback({ + text: `Error creating token: ${error.message}`, + content: { error: error.message }, + }); + } + return false; + } }, examples: [ [ From 347075dfbb055357a3dea270f8c70c48eb151a71 Mon Sep 17 00:00:00 2001 From: xiaohuo Date: Sun, 22 Dec 2024 20:35:33 +0800 Subject: [PATCH 4/7] chore: clean up --- .../plugin-solana-agentkit/src/bignumber.ts | 9 - .../plugin-solana-agentkit/src/environment.ts | 76 ----- .../src/keypairUtils.ts | 82 ----- .../src/tests/token.test.ts | 134 -------- .../plugin-solana-agentkit/src/types/token.ts | 302 ------------------ 5 files changed, 603 deletions(-) delete mode 100644 packages/plugin-solana-agentkit/src/bignumber.ts delete mode 100644 packages/plugin-solana-agentkit/src/environment.ts delete mode 100644 packages/plugin-solana-agentkit/src/keypairUtils.ts delete mode 100644 packages/plugin-solana-agentkit/src/tests/token.test.ts delete mode 100644 packages/plugin-solana-agentkit/src/types/token.ts diff --git a/packages/plugin-solana-agentkit/src/bignumber.ts b/packages/plugin-solana-agentkit/src/bignumber.ts deleted file mode 100644 index f320676a0f..0000000000 --- a/packages/plugin-solana-agentkit/src/bignumber.ts +++ /dev/null @@ -1,9 +0,0 @@ -import BigNumber from "bignumber.js"; - -// Re-export BigNumber constructor -export const BN = BigNumber; - -// Helper function to create new BigNumber instances -export function toBN(value: string | number | BigNumber): BigNumber { - return new BigNumber(value); -} diff --git a/packages/plugin-solana-agentkit/src/environment.ts b/packages/plugin-solana-agentkit/src/environment.ts deleted file mode 100644 index e6931091c8..0000000000 --- a/packages/plugin-solana-agentkit/src/environment.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { IAgentRuntime } from "@elizaos/core"; -import { z } from "zod"; - -export const solanaEnvSchema = z - .object({ - WALLET_SECRET_SALT: z.string().optional(), - }) - .and( - z.union([ - z.object({ - WALLET_SECRET_KEY: z - .string() - .min(1, "Wallet secret key is required"), - WALLET_PUBLIC_KEY: z - .string() - .min(1, "Wallet public key is required"), - }), - z.object({ - WALLET_SECRET_SALT: z - .string() - .min(1, "Wallet secret salt is required"), - }), - ]) - ) - .and( - z.object({ - SOL_ADDRESS: z.string().min(1, "SOL address is required"), - SLIPPAGE: z.string().min(1, "Slippage is required"), - RPC_URL: z.string().min(1, "RPC URL is required"), - HELIUS_API_KEY: z.string().min(1, "Helius API key is required"), - BIRDEYE_API_KEY: z.string().min(1, "Birdeye API key is required"), - }) - ); - -export type SolanaConfig = z.infer; - -export async function validateSolanaConfig( - runtime: IAgentRuntime -): Promise { - try { - const config = { - WALLET_SECRET_SALT: - runtime.getSetting("WALLET_SECRET_SALT") || - process.env.WALLET_SECRET_SALT, - WALLET_SECRET_KEY: - runtime.getSetting("WALLET_SECRET_KEY") || - process.env.WALLET_SECRET_KEY, - WALLET_PUBLIC_KEY: - runtime.getSetting("SOLANA_PUBLIC_KEY") || - runtime.getSetting("WALLET_PUBLIC_KEY") || - process.env.WALLET_PUBLIC_KEY, - SOL_ADDRESS: - runtime.getSetting("SOL_ADDRESS") || process.env.SOL_ADDRESS, - SLIPPAGE: runtime.getSetting("SLIPPAGE") || process.env.SLIPPAGE, - RPC_URL: runtime.getSetting("RPC_URL") || process.env.RPC_URL, - HELIUS_API_KEY: - runtime.getSetting("HELIUS_API_KEY") || - process.env.HELIUS_API_KEY, - BIRDEYE_API_KEY: - runtime.getSetting("BIRDEYE_API_KEY") || - process.env.BIRDEYE_API_KEY, - }; - - return solanaEnvSchema.parse(config); - } catch (error) { - if (error instanceof z.ZodError) { - const errorMessages = error.errors - .map((err) => `${err.path.join(".")}: ${err.message}`) - .join("\n"); - throw new Error( - `Solana configuration validation failed:\n${errorMessages}` - ); - } - throw error; - } -} diff --git a/packages/plugin-solana-agentkit/src/keypairUtils.ts b/packages/plugin-solana-agentkit/src/keypairUtils.ts deleted file mode 100644 index 4aa942ebed..0000000000 --- a/packages/plugin-solana-agentkit/src/keypairUtils.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Keypair, PublicKey } from "@solana/web3.js"; -import { DeriveKeyProvider, TEEMode } from "@elizaos/plugin-tee"; -import bs58 from "bs58"; -import { IAgentRuntime } from "@elizaos/core"; - -export interface KeypairResult { - keypair?: Keypair; - publicKey?: PublicKey; -} - -/** - * Gets either a keypair or public key based on TEE mode and runtime settings - * @param runtime The agent runtime - * @param requirePrivateKey Whether to return a full keypair (true) or just public key (false) - * @returns KeypairResult containing either keypair or public key - */ -export async function getWalletKey( - runtime: IAgentRuntime, - requirePrivateKey: boolean = true -): Promise { - const teeMode = runtime.getSetting("TEE_MODE") || TEEMode.OFF; - - if (teeMode !== TEEMode.OFF) { - const walletSecretSalt = runtime.getSetting("WALLET_SECRET_SALT"); - if (!walletSecretSalt) { - throw new Error( - "WALLET_SECRET_SALT required when TEE_MODE is enabled" - ); - } - - const deriveKeyProvider = new DeriveKeyProvider(teeMode); - const deriveKeyResult = await deriveKeyProvider.deriveEd25519Keypair( - "/", - walletSecretSalt, - runtime.agentId - ); - - return requirePrivateKey - ? { keypair: deriveKeyResult.keypair } - : { publicKey: deriveKeyResult.keypair.publicKey }; - } - - // TEE mode is OFF - if (requirePrivateKey) { - const privateKeyString = - runtime.getSetting("SOLANA_PRIVATE_KEY") ?? - runtime.getSetting("WALLET_PRIVATE_KEY"); - - if (!privateKeyString) { - throw new Error("Private key not found in settings"); - } - - try { - // First try base58 - const secretKey = bs58.decode(privateKeyString); - return { keypair: Keypair.fromSecretKey(secretKey) }; - } catch (e) { - console.log("Error decoding base58 private key:", e); - try { - // Then try base64 - console.log("Try decoding base64 instead"); - const secretKey = Uint8Array.from( - Buffer.from(privateKeyString, "base64") - ); - return { keypair: Keypair.fromSecretKey(secretKey) }; - } catch (e2) { - console.error("Error decoding private key: ", e2); - throw new Error("Invalid private key format"); - } - } - } else { - const publicKeyString = - runtime.getSetting("SOLANA_PUBLIC_KEY") ?? - runtime.getSetting("WALLET_PUBLIC_KEY"); - - if (!publicKeyString) { - throw new Error("Public key not found in settings"); - } - - return { publicKey: new PublicKey(publicKeyString) }; - } -} diff --git a/packages/plugin-solana-agentkit/src/tests/token.test.ts b/packages/plugin-solana-agentkit/src/tests/token.test.ts deleted file mode 100644 index 6b799c1c23..0000000000 --- a/packages/plugin-solana-agentkit/src/tests/token.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"; -import { TokenProvider } from "../providers/token.ts"; - -// Mock NodeCache -vi.mock("node-cache", () => { - return { - default: vi.fn().mockImplementation(() => ({ - set: vi.fn(), - get: vi.fn().mockReturnValue(null), - })), - }; -}); - -// Mock path module -vi.mock("path", async () => { - const actual = await vi.importActual("path"); - return { - ...(actual as any), - join: vi.fn().mockImplementation((...args) => args.join("/")), - }; -}); - -// Mock the WalletProvider -const mockWalletProvider = { - fetchPortfolioValue: vi.fn(), -}; - -// Mock the ICacheManager -const mockCacheManager = { - get: vi.fn().mockResolvedValue(null), - set: vi.fn(), -}; - -// Mock fetch globally -const mockFetch = vi.fn(); -global.fetch = mockFetch; - -describe("TokenProvider", () => { - let tokenProvider: TokenProvider; - const TEST_TOKEN_ADDRESS = "2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh"; - - beforeEach(() => { - vi.clearAllMocks(); - mockCacheManager.get.mockResolvedValue(null); - - // Create new instance of TokenProvider with mocked dependencies - tokenProvider = new TokenProvider( - TEST_TOKEN_ADDRESS, - mockWalletProvider as any, - mockCacheManager as any - ); - }); - - afterEach(() => { - vi.clearAllTimers(); - }); - - describe("Cache Management", () => { - it("should use cached data when available", async () => { - const mockData = { test: "data" }; - mockCacheManager.get.mockResolvedValueOnce(mockData); - - const result = await (tokenProvider as any).getCachedData( - "test-key" - ); - - expect(result).toEqual(mockData); - expect(mockCacheManager.get).toHaveBeenCalledTimes(1); - }); - - it("should write data to both caches", async () => { - const testData = { test: "data" }; - - await (tokenProvider as any).setCachedData("test-key", testData); - - expect(mockCacheManager.set).toHaveBeenCalledWith( - expect.stringContaining("test-key"), - testData, - expect.any(Object) - ); - }); - }); - - describe("Wallet Integration", () => { - it("should fetch tokens in wallet", async () => { - const mockItems = [ - { symbol: "SOL", address: "address1" }, - { symbol: "BTC", address: "address2" }, - ]; - - mockWalletProvider.fetchPortfolioValue.mockResolvedValueOnce({ - items: mockItems, - }); - - const result = await tokenProvider.getTokensInWallet({} as any); - - expect(result).toEqual(mockItems); - expect( - mockWalletProvider.fetchPortfolioValue - ).toHaveBeenCalledTimes(1); - }); - - it("should find token in wallet by symbol", async () => { - const mockItems = [ - { symbol: "SOL", address: "address1" }, - { symbol: "BTC", address: "address2" }, - ]; - - mockWalletProvider.fetchPortfolioValue.mockResolvedValueOnce({ - items: mockItems, - }); - - const result = await tokenProvider.getTokenFromWallet( - {} as any, - "SOL" - ); - - expect(result).toBe("address1"); - }); - - it("should return null for token not in wallet", async () => { - mockWalletProvider.fetchPortfolioValue.mockResolvedValueOnce({ - items: [], - }); - - const result = await tokenProvider.getTokenFromWallet( - {} as any, - "NONEXISTENT" - ); - - expect(result).toBeNull(); - }); - }); -}); diff --git a/packages/plugin-solana-agentkit/src/types/token.ts b/packages/plugin-solana-agentkit/src/types/token.ts deleted file mode 100644 index 1fca4c37c3..0000000000 --- a/packages/plugin-solana-agentkit/src/types/token.ts +++ /dev/null @@ -1,302 +0,0 @@ -export interface TokenSecurityData { - ownerBalance: string; - creatorBalance: string; - ownerPercentage: number; - creatorPercentage: number; - top10HolderBalance: string; - top10HolderPercent: number; -} - -export interface TokenCodex { - id: string; - address: string; - cmcId: number; - decimals: number; - name: string; - symbol: string; - totalSupply: string; - circulatingSupply: string; - imageThumbUrl: string; - blueCheckmark: boolean; - isScam: boolean; -} - -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; - unique_wallet_1h: number; - unique_wallet_history_1h: number; - unique_wallet_1h_change_percent: number; - unique_wallet_2h: number; - unique_wallet_history_2h: number; - unique_wallet_2h_change_percent: number; - unique_wallet_4h: number; - unique_wallet_history_4h: number; - unique_wallet_4h_change_percent: number; - unique_wallet_8h: number; - unique_wallet_history_8h: number | null; - unique_wallet_8h_change_percent: number | null; - unique_wallet_24h: number; - unique_wallet_history_24h: number | null; - 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 | null; - trade_8h_change_percent: number | null; - sell_8h: number; - sell_history_8h: number | null; - sell_8h_change_percent: number | null; - buy_8h: number; - buy_history_8h: number | null; - buy_8h_change_percent: number | null; - volume_8h: number; - volume_8h_usd: number; - volume_history_8h: number; - volume_history_8h_usd: number; - volume_8h_change_percent: number | null; - 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 | null; - 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 | null; - trade_24h: number; - trade_history_24h: number; - trade_24h_change_percent: number | null; - sell_24h: number; - sell_history_24h: number; - sell_24h_change_percent: number | null; - buy_24h: number; - buy_history_24h: number; - buy_24h_change_percent: number | null; - volume_24h: number; - volume_24h_usd: number; - volume_history_24h: number; - volume_history_24h_usd: number; - volume_24h_change_percent: number | null; - 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 | null; - 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 | null; -} - -export interface HolderData { - address: string; - balance: string; -} - -export interface ProcessedTokenData { - security: TokenSecurityData; - tradeData: TokenTradeData; - holderDistributionTrend: string; // 'increasing' | 'decreasing' | 'stable' - highValueHolders: Array<{ - holderAddress: string; - balanceUsd: string; - }>; - recentTrades: boolean; - highSupplyHoldersCount: number; - dexScreenerData: DexScreenerData; - - isDexScreenerListed: boolean; - isDexScreenerPaid: boolean; - tokenCodex: TokenCodex; -} - -export interface DexScreenerPair { - chainId: string; - dexId: string; - url: string; - pairAddress: string; - baseToken: { - address: string; - name: string; - symbol: string; - }; - quoteToken: { - address: string; - name: string; - symbol: string; - }; - priceNative: string; - priceUsd: string; - txns: { - m5: { buys: number; sells: number }; - h1: { buys: number; sells: number }; - h6: { buys: number; sells: number }; - h24: { buys: number; sells: number }; - }; - volume: { - h24: number; - h6: number; - h1: number; - m5: number; - }; - priceChange: { - m5: number; - h1: number; - h6: number; - h24: number; - }; - liquidity: { - usd: number; - base: number; - quote: number; - }; - fdv: number; - marketCap: number; - pairCreatedAt: number; - info: { - imageUrl: string; - websites: { label: string; url: string }[]; - socials: { type: string; url: string }[]; - }; - boosts: { - active: number; - }; -} - -export interface DexScreenerData { - schemaVersion: string; - pairs: DexScreenerPair[]; -} - -export interface Prices { - solana: { usd: string }; - bitcoin: { usd: string }; - ethereum: { usd: string }; -} - -export interface CalculatedBuyAmounts { - none: 0; - low: number; - medium: number; - high: number; -} From bdbc2feb71356789307525ae87ea965a5efc3dfb Mon Sep 17 00:00:00 2001 From: xiaohuo Date: Sun, 22 Dec 2024 20:42:03 +0800 Subject: [PATCH 5/7] fix: lint --- .../src/actions/createToken.ts | 7 +- .../src/actions/transfer.ts | 263 ------------------ packages/plugin-solana-agentkit/src/index.ts | 2 +- 3 files changed, 3 insertions(+), 269 deletions(-) delete mode 100644 packages/plugin-solana-agentkit/src/actions/transfer.ts diff --git a/packages/plugin-solana-agentkit/src/actions/createToken.ts b/packages/plugin-solana-agentkit/src/actions/createToken.ts index a225cc1bbb..08aba878f4 100644 --- a/packages/plugin-solana-agentkit/src/actions/createToken.ts +++ b/packages/plugin-solana-agentkit/src/actions/createToken.ts @@ -22,10 +22,7 @@ export interface CreateTokenContent extends Content { initialSupply: number; } -function isCreateTokenContent( - runtime: IAgentRuntime, - content: any -): content is CreateTokenContent { +function isCreateTokenContent(content: any): content is CreateTokenContent { elizaLogger.log("Content for createToken", content); return ( typeof content.name === "string" && @@ -94,7 +91,7 @@ export default { }); // Validate transfer content - if (!isCreateTokenContent(runtime, content)) { + if (!isCreateTokenContent(content)) { elizaLogger.error("Invalid content for CREATE_TOKEN action."); if (callback) { callback({ diff --git a/packages/plugin-solana-agentkit/src/actions/transfer.ts b/packages/plugin-solana-agentkit/src/actions/transfer.ts deleted file mode 100644 index 118e2b2468..0000000000 --- a/packages/plugin-solana-agentkit/src/actions/transfer.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { - getAssociatedTokenAddressSync, - createTransferInstruction, -} from "@solana/spl-token"; -import { elizaLogger, settings } from "@elizaos/core"; - -import { - Connection, - PublicKey, - TransactionMessage, - VersionedTransaction, -} from "@solana/web3.js"; - -import { - ActionExample, - Content, - HandlerCallback, - IAgentRuntime, - Memory, - ModelClass, - State, - type Action, -} from "@elizaos/core"; -import { composeContext } from "@elizaos/core"; -import { getWalletKey } from "../keypairUtils"; -import { generateObjectDeprecated } from "@elizaos/core"; - -export interface TransferContent extends Content { - tokenAddress: string; - recipient: string; - amount: string | number; -} - -function isTransferContent( - runtime: IAgentRuntime, - content: any -): content is TransferContent { - console.log("Content for transfer", content); - return ( - typeof content.tokenAddress === "string" && - typeof content.recipient === "string" && - (typeof content.amount === "string" || - typeof content.amount === "number") - ); -} - -const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. - -Example response: -\`\`\`json -{ - "tokenAddress": "BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump", - "recipient": "9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", - "amount": "1000" -} -\`\`\` - -{{recentMessages}} - -Given the recent messages, extract the following information about the requested token transfer: -- Token contract address -- Recipient wallet address -- Amount to transfer - -Respond with a JSON markdown block containing only the extracted values.`; - -export default { - name: "SEND_TOKEN", - similes: [ - "TRANSFER_TOKEN", - "TRANSFER_TOKENS", - "SEND_TOKENS", - "SEND_SOL", - "PAY", - ], - validate: async (runtime: IAgentRuntime, message: Memory) => { - console.log("Validating transfer from user:", message.userId); - //add custom validate logic here - /* - const adminIds = runtime.getSetting("ADMIN_USER_IDS")?.split(",") || []; - //console.log("Admin IDs from settings:", adminIds); - - const isAdmin = adminIds.includes(message.userId); - - if (isAdmin) { - //console.log(`Authorized transfer from user: ${message.userId}`); - return true; - } - else - { - //console.log(`Unauthorized transfer attempt from user: ${message.userId}`); - return false; - } - */ - return false; - }, - description: "Transfer tokens from the agent's wallet to another address", - handler: async ( - runtime: IAgentRuntime, - message: Memory, - state: State, - _options: { [key: string]: unknown }, - callback?: HandlerCallback - ): Promise => { - elizaLogger.log("Starting SEND_TOKEN handler..."); - - // Initialize or update state - if (!state) { - state = (await runtime.composeState(message)) as State; - } else { - state = await runtime.updateRecentMessageState(state); - } - - // Compose transfer context - const transferContext = composeContext({ - state, - template: transferTemplate, - }); - - // Generate transfer content - const content = await generateObjectDeprecated({ - runtime, - context: transferContext, - modelClass: ModelClass.LARGE, - }); - - // Validate transfer content - if (!isTransferContent(runtime, content)) { - console.error("Invalid content for TRANSFER_TOKEN action."); - if (callback) { - callback({ - text: "Unable to process transfer request. Invalid content provided.", - content: { error: "Invalid transfer content" }, - }); - } - return false; - } - - try { - const { keypair: senderKeypair } = await getWalletKey( - runtime, - true - ); - - const connection = new Connection(settings.RPC_URL!); - - const mintPubkey = new PublicKey(content.tokenAddress); - const recipientPubkey = new PublicKey(content.recipient); - - // Get decimals (simplest way) - const mintInfo = await connection.getParsedAccountInfo(mintPubkey); - const decimals = - (mintInfo.value?.data as any)?.parsed?.info?.decimals ?? 9; - - // Adjust amount with decimals - const adjustedAmount = BigInt( - Number(content.amount) * Math.pow(10, decimals) - ); - console.log( - `Transferring: ${content.amount} tokens (${adjustedAmount} base units)` - ); - - // Rest of the existing working code... - const senderATA = getAssociatedTokenAddressSync( - mintPubkey, - senderKeypair.publicKey - ); - const recipientATA = getAssociatedTokenAddressSync( - mintPubkey, - recipientPubkey - ); - - const instructions = []; - - const recipientATAInfo = - await connection.getAccountInfo(recipientATA); - if (!recipientATAInfo) { - const { createAssociatedTokenAccountInstruction } = - await import("@solana/spl-token"); - instructions.push( - createAssociatedTokenAccountInstruction( - senderKeypair.publicKey, - recipientATA, - recipientPubkey, - mintPubkey - ) - ); - } - - instructions.push( - createTransferInstruction( - senderATA, - recipientATA, - senderKeypair.publicKey, - adjustedAmount - ) - ); - - // Create and sign versioned transaction - const messageV0 = new TransactionMessage({ - payerKey: senderKeypair.publicKey, - recentBlockhash: (await connection.getLatestBlockhash()) - .blockhash, - instructions, - }).compileToV0Message(); - - const transaction = new VersionedTransaction(messageV0); - transaction.sign([senderKeypair]); - - // Send transaction - const signature = await connection.sendTransaction(transaction); - - console.log("Transfer successful:", signature); - - if (callback) { - callback({ - text: `Successfully transferred ${content.amount} tokens to ${content.recipient}\nTransaction: ${signature}`, - content: { - success: true, - signature, - amount: content.amount, - recipient: content.recipient, - }, - }); - } - - return true; - } catch (error) { - console.error("Error during token transfer:", error); - if (callback) { - callback({ - text: `Error transferring tokens: ${error.message}`, - content: { error: error.message }, - }); - } - return false; - } - }, - - examples: [ - [ - { - user: "{{user1}}", - content: { - text: "Send 69 EZSIS BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", - }, - }, - { - user: "{{user2}}", - content: { - text: "I'll send 69 EZSIS tokens now...", - action: "SEND_TOKEN", - }, - }, - { - user: "{{user2}}", - content: { - text: "Successfully sent 69 EZSIS tokens to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa\nTransaction: 5KtPn3DXXzHkb7VAVHZGwXJQqww39ASnrf7YkyJoF2qAGEpBEEGvRHLnnTG8ZVwKqNHMqSckWVGnsQAgfH5pbxEb", - }, - }, - ], - ] as ActionExample[][], -} as Action; diff --git a/packages/plugin-solana-agentkit/src/index.ts b/packages/plugin-solana-agentkit/src/index.ts index 3a7749a528..ae80c66c74 100644 --- a/packages/plugin-solana-agentkit/src/index.ts +++ b/packages/plugin-solana-agentkit/src/index.ts @@ -6,7 +6,7 @@ export const solanaAgentkitPlguin: Plugin = { description: "Solana Plugin with solana agent kit for Eliza", actions: [createToken], evaluators: [], - providers: [,], + providers: [], }; export default solanaAgentkitPlguin; From 7b8fd8d9101c627f23eb7bd140600b3d566b6275 Mon Sep 17 00:00:00 2001 From: xiaohuo Date: Sun, 22 Dec 2024 20:46:46 +0800 Subject: [PATCH 6/7] fix: typo --- packages/plugin-solana-agentkit/src/actions/createToken.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-solana-agentkit/src/actions/createToken.ts b/packages/plugin-solana-agentkit/src/actions/createToken.ts index 08aba878f4..f3b718820a 100644 --- a/packages/plugin-solana-agentkit/src/actions/createToken.ts +++ b/packages/plugin-solana-agentkit/src/actions/createToken.ts @@ -151,7 +151,7 @@ export default { { user: "{{user2}}", content: { - text: "I'll creaete token now...", + text: "I'll create token now...", action: "CREATE_TOKEN", }, }, From 25b484a506ab56818ae4f517a39de82e7c61c52e Mon Sep 17 00:00:00 2001 From: xiaohuo Date: Sun, 22 Dec 2024 21:14:48 +0800 Subject: [PATCH 7/7] fix: more log --- .../plugin-solana-agentkit/src/actions/createToken.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/plugin-solana-agentkit/src/actions/createToken.ts b/packages/plugin-solana-agentkit/src/actions/createToken.ts index f3b718820a..b0720348d5 100644 --- a/packages/plugin-solana-agentkit/src/actions/createToken.ts +++ b/packages/plugin-solana-agentkit/src/actions/createToken.ts @@ -112,19 +112,21 @@ export default { openAIKey ); try { - const deployedAddress = solanaAgentKit.deployToken( + const deployedAddress = await solanaAgentKit.deployToken( content.name, content.uri, content.symbol, - content.decimals, - content.initialSupply + content.decimals + // content.initialSupply comment out this cause the sdk has some issue with this parameter ); elizaLogger.log("Create successful: ", deployedAddress); + elizaLogger.log(deployedAddress); if (callback) { callback({ text: `Successfully create token ${content.name}`, content: { success: true, + deployedAddress, }, }); }