UNPKG

@symlabs/plugin-defi

Version:

DeFi operations plugin for ElizaOS - Fixed Jupiter API integration, Solana swaps, MEV protection

1,494 lines (1,486 loc) 75.4 kB
// src/index.ts import { logger } from "@elizaos/core"; // src/services/SolanaAgentService.ts import { Service, elizaLogger as elizaLogger2 } from "@elizaos/core"; import { Connection, Keypair, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js"; import bs58 from "bs58"; // src/types.ts var ANUBIS_CONSTANTS = { DEFAULT_SLIPPAGE: 50, // 0.5% MAX_SLIPPAGE: 1e3, // 10% MIN_SOL_BALANCE: 0.01, // Minimum SOL to maintain MAX_RETRY_ATTEMPTS: 3, TRANSACTION_TIMEOUT: 6e4, // 60 seconds HEALTH_CHECK_INTERVAL: 3e4, // 30 seconds MEV_REBATE_PERCENTAGE: 50, // 50% via Helius SUPPORTED_TOKENS: [ "So11111111111111111111111111111111111111112", // SOL "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", // USDT "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", // mSOL "bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1" // bSOL ], SUPPORTED_PROTOCOLS: [ "jupiter", "raydium", "orca", "marinade", "lido", "kamino", "marginfi", "solend" ] }; // src/utils/decimal.ts import Decimal from "decimal.js"; import { elizaLogger } from "@elizaos/core"; Decimal.set({ precision: 20, // 20 significant digits rounding: Decimal.ROUND_DOWN, // Always round down for safety toExpNeg: -9, // Use normal notation for small numbers toExpPos: 20 // Use normal notation for large numbers }); function toSmallestUnit(amount, decimals) { try { const decimal = new Decimal(amount); const multiplier = new Decimal(10).pow(decimals); const result = decimal.mul(multiplier); return result.toFixed(0); } catch (error) { elizaLogger.error("Failed to convert to smallest unit:", error); throw new Error(`Invalid amount: ${amount}`); } } function fromSmallestUnit(amount, decimals) { try { const decimal = new Decimal(amount); const divisor = new Decimal(10).pow(decimals); const result = decimal.div(divisor); return result.toFixed(); } catch (error) { elizaLogger.error("Failed to convert from smallest unit:", error); throw new Error(`Invalid amount: ${amount}`); } } function formatTokenAmount(amount, decimals, displayDecimals = 6) { try { const decimal = new Decimal(amount); const divisor = new Decimal(10).pow(decimals); const result = decimal.div(divisor); return result.toFixed(displayDecimals); } catch (error) { elizaLogger.error("Failed to format token amount:", error); return "0"; } } function isValidAmount(amount, maxDecimals) { try { const decimal = new Decimal(amount); if (decimal.lte(0)) { return false; } if (maxDecimals !== void 0) { const parts = amount.split("."); if (parts.length > 1 && parts[1].length > maxDecimals) { return false; } } return true; } catch { return false; } } function compareAmounts(amount1, amount2) { try { const decimal1 = new Decimal(amount1); const decimal2 = new Decimal(amount2); return decimal1.comparedTo(decimal2); } catch (error) { elizaLogger.error("Failed to compare amounts:", error); return 0; } } function calculateUsdValue(amount, price) { try { const decimal = new Decimal(amount); const priceDecimal = new Decimal(price); return decimal.mul(priceDecimal).toFixed(2); } catch (error) { elizaLogger.error("Failed to calculate USD value:", error); return "0"; } } // src/services/SolanaAgentService.ts var SolanaAgentService = class _SolanaAgentService extends Service { static serviceType = "solana-agent"; capabilityDescription = "Core Solana blockchain operations and agent connectivity"; // Static start method required by Service base class static async start(runtime) { const service = new _SolanaAgentService(runtime); await service.initialize(runtime); return service; } // Static stop method required by Service base class static async stop(runtime) { return; } connection = null; wallet = null; anubisConfig = null; state; healthCheckInterval = null; constructor(runtime) { super(runtime); this.state = { isConnected: false, lastHealthCheck: 0, version: "1.0.0", capabilities: [ "balance_check", "token_transfer", "transaction_signing", "account_monitoring" ], errors: [] }; } async initialize(runtime) { try { elizaLogger2.info("\u{1F531} Initializing Solana Agent Service..."); const privateKey = runtime.getSetting("SOLANA_PRIVATE_KEY") || process.env.SOLANA_PRIVATE_KEY; const rpcUrl = runtime.getSetting("SOLANA_RPC_URL") || process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com"; if (!privateKey) { elizaLogger2.warn("\u26A0\uFE0F SOLANA_PRIVATE_KEY not configured - service will run in limited mode"); this.state.errors.push({ code: "CONFIG_MISSING", message: "SOLANA_PRIVATE_KEY not configured", details: "Service running in limited mode without wallet access", timestamp: Date.now() }); return; } this.anubisConfig = { wallet: Keypair.fromSecretKey(bs58.decode(privateKey)), rpcUrl, heliusApiKey: runtime.getSetting("HELIUS_API_KEY"), jupiterApiKey: runtime.getSetting("JUPITER_API_KEY") }; this.connection = new Connection(this.anubisConfig.rpcUrl, "confirmed"); this.wallet = this.anubisConfig.wallet; const slot = await this.connection.getSlot(); elizaLogger2.info(`\u{1F4E1} Connected to Solana at slot ${slot}`); const balance = await this.connection.getBalance(this.wallet.publicKey); elizaLogger2.info(`\u{1F4B0} Wallet: ${this.wallet.publicKey.toString()}`); elizaLogger2.info(`\u{1F4B0} Balance: ${fromSmallestUnit(balance, 9)} SOL`); if (balance < ANUBIS_CONSTANTS.MIN_SOL_BALANCE * LAMPORTS_PER_SOL) { elizaLogger2.warn(`\u26A0\uFE0F Low SOL balance: ${fromSmallestUnit(balance, 9)} SOL`); } this.state.isConnected = true; this.state.lastHealthCheck = Date.now(); this.startHealthCheck(); elizaLogger2.info("\u2705 Solana Agent Service initialized successfully"); } catch (error) { const anubisError = { code: "SOLANA_INIT_FAILED", message: "Failed to initialize Solana Agent Service", details: error, timestamp: Date.now() }; this.state.errors.push(anubisError); elizaLogger2.error("\u274C Solana Agent Service initialization failed:", error); throw error; } } startHealthCheck() { this.healthCheckInterval = setInterval(async () => { try { if (!this.connection) { throw new Error("Solana connection lost during health check. Attempting to reconnect..."); } const slot = await this.connection.getSlot(); this.state.lastHealthCheck = Date.now(); this.state.isConnected = true; } catch (error) { elizaLogger2.warn("Health check failed:", error); this.state.isConnected = false; const anubisError = { code: "HEALTH_CHECK_FAILED", message: "Solana connection health check failed", details: error, timestamp: Date.now() }; this.state.errors.push(anubisError); if (this.state.errors.length > 10) { this.state.errors = this.state.errors.slice(-10); } } }, ANUBIS_CONSTANTS.HEALTH_CHECK_INTERVAL); } async stop() { elizaLogger2.info("Stopping Solana Agent Service..."); if (this.healthCheckInterval) { clearInterval(this.healthCheckInterval); this.healthCheckInterval = null; } this.state.isConnected = false; } // ============= Public Methods ============= /** * Get the Solana connection instance * @returns {Connection} The active Solana RPC connection * @throws {Error} If connection is not initialized */ getConnection() { if (!this.connection) { throw new Error("Solana connection not initialized. Call initialize() before using this method."); } return this.connection; } getWallet() { if (!this.wallet) { throw new Error("Wallet not initialized. Ensure SOLANA_PRIVATE_KEY is configured and initialize() was called."); } return this.wallet; } getWalletAddress() { if (!this.wallet) { throw new Error("Wallet not initialized. Ensure SOLANA_PRIVATE_KEY is configured and initialize() was called."); } return this.wallet.publicKey.toString(); } getState() { return { ...this.state }; } async getSolBalance() { try { if (!this.connection || !this.wallet) { throw new Error("SolanaAgentService not initialized: Connection or wallet is null. Ensure initialize() was called successfully."); } const balance = await this.connection.getBalance(this.wallet.publicKey); return parseFloat(fromSmallestUnit(balance, 9)); } catch (error) { elizaLogger2.error("Failed to get SOL balance:", error); throw error; } } async getTokenBalances() { try { if (!this.connection || !this.wallet) { throw new Error("SolanaAgentService not initialized: Connection or wallet is null. Ensure initialize() was called successfully."); } const tokenAccounts = await this.connection.getParsedTokenAccountsByOwner( this.wallet.publicKey, { programId: new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") } ); const balances = []; for (const account of tokenAccounts.value) { const parsedInfo = account.account.data.parsed.info; const tokenAmount = parsedInfo.tokenAmount; if (tokenAmount.uiAmount > 0) { balances.push({ mint: parsedInfo.mint, symbol: "", // Will be populated by token registry name: "", amount: tokenAmount.amount, decimals: tokenAmount.decimals, usdValue: 0, // Will be populated by price service price: 0, change24h: 0 }); } } const solBalance = await this.getSolBalance(); balances.push({ mint: "So11111111111111111111111111111111111111112", symbol: "SOL", name: "Solana", amount: (solBalance * LAMPORTS_PER_SOL).toString(), decimals: 9, usdValue: 0, // Will be populated by price service price: 0, change24h: 0 }); return balances; } catch (error) { elizaLogger2.error("Failed to get token balances:", error); throw error; } } async validateTransaction(transaction) { try { const txBuffer = Buffer.from(transaction, "base64"); return txBuffer.length > 0; } catch (error) { elizaLogger2.error("Transaction validation failed:", error); return false; } } async estimateTransactionFee(transaction) { try { if (!this.connection) { throw new Error("Solana connection not available. Service may not be initialized properly."); } const recentBlockhash = await this.connection.getLatestBlockhash(); return 5e3; } catch (error) { elizaLogger2.error("Fee estimation failed:", error); return 5e3; } } async sendTransaction(transaction, options) { try { if (!this.connection) { throw new Error("Solana connection not available. Service may not be initialized properly."); } const response = await this.connection.sendRawTransaction( Buffer.from(transaction, "base64"), { skipPreflight: options?.skipPreflight || false, maxRetries: options?.maxRetries || ANUBIS_CONSTANTS.MAX_RETRY_ATTEMPTS } ); elizaLogger2.info(`\u{1F4E4} Transaction sent: ${response}`); return response; } catch (error) { elizaLogger2.error("Transaction send failed:", error); throw error; } } async confirmTransaction(signature) { try { if (!this.connection) { throw new Error("Solana connection not available. Service may not be initialized properly."); } const confirmation = await this.connection.confirmTransaction( signature, "confirmed" ); if (confirmation.value.err) { elizaLogger2.error("Transaction failed:", confirmation.value.err); return false; } elizaLogger2.info(`\u2705 Transaction confirmed: ${signature}`); return true; } catch (error) { elizaLogger2.error("Transaction confirmation failed:", error); return false; } } async getTransactionStatus(signature) { try { if (!this.connection) { throw new Error("Solana connection not available. Service may not be initialized properly."); } const status = await this.connection.getSignatureStatus(signature); if (!status.value) { return { status: "pending" }; } if (status.value.err) { return { status: "failed", error: JSON.stringify(status.value.err) }; } return { status: status.value.confirmationStatus, confirmations: status.value.confirmations || 0 }; } catch (error) { elizaLogger2.error("Failed to get transaction status:", error); return { status: "failed", error: error.message }; } } // ============= Utility Methods ============= isValidSolanaAddress(address) { try { new PublicKey(address); return true; } catch { return false; } } formatSolAmount(lamports) { return (lamports / LAMPORTS_PER_SOL).toFixed(6); } parseSolAmount(solAmount) { const lamports = toSmallestUnit(solAmount, 9); return parseInt(lamports, 10); } // ============= Error Handling ============= getLastError() { return this.state.errors.length > 0 ? this.state.errors[this.state.errors.length - 1] : null; } clearErrors() { this.state.errors = []; } // ============= Service Lifecycle ============= async restart() { elizaLogger2.info("\u{1F504} Restarting Solana Agent Service..."); await this.stop(); await this.initialize(this.runtime); } }; var SolanaAgentService_default = SolanaAgentService; // src/services/JupiterService.ts import { Service as Service2, elizaLogger as elizaLogger3 } from "@elizaos/core"; var JupiterService = class _JupiterService extends Service2 { static serviceType = "jupiter"; capabilityDescription = "Jupiter DEX aggregation for optimal token swapping with MEV protection"; // Static start method required by Service base class static async start(runtime) { const service = new _JupiterService(runtime); await service.initialize(); return service; } // Static stop method required by Service base class static async stop(runtime) { return; } jupiterApiUrl = "https://api.jup.ag"; priceApiUrl = "https://api.jup.ag/price/v2"; solanaService = null; state; tokenList = /* @__PURE__ */ new Map(); constructor(runtime) { super(runtime); this.state = { isConnected: false, lastHealthCheck: 0, version: "6.0.0", capabilities: [ "swap_quotes", "swap_execution", "price_data", "route_optimization", "slippage_protection" ], errors: [] }; } async initialize() { try { elizaLogger3.info("\u{1F680} Initializing Jupiter Service..."); this.solanaService = this.runtime.getService("solana-agent"); if (!this.solanaService) { throw new Error("Solana Agent Service not found"); } await this.testConnection(); await this.loadTokenList(); this.state.isConnected = true; this.state.lastHealthCheck = Date.now(); elizaLogger3.info("\u2705 Jupiter Service initialized successfully"); } catch (error) { const anubisError = { code: "JUPITER_INIT_FAILED", message: "Failed to initialize Jupiter Service", details: error, timestamp: Date.now() }; this.state.errors.push(anubisError); elizaLogger3.error("\u274C Jupiter Service initialization failed:", error); throw error; } } async stop() { elizaLogger3.info("Stopping Jupiter Service..."); this.state.isConnected = false; } async testConnection() { try { const testUrl = "https://quote-api.jup.ag/v6/quote?inputMint=So11111111111111111111111111111111111111112&outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&amount=1000000"; const response = await fetch(testUrl); if (!response.ok) { elizaLogger3.warn(`\u26A0\uFE0F Jupiter API test returned ${response.status} - service will run with limited functionality`); } else { elizaLogger3.info("\u{1F4E1} Jupiter API connection verified"); } } catch (error) { elizaLogger3.warn(`\u26A0\uFE0F Jupiter API connection test failed: ${error.message} - continuing with limited functionality`); } } async loadTokenList() { try { const response = await fetch(`${this.jupiterApiUrl}/tokens`, { headers: { 'x-api-key': process.env.JUPITER_API_KEY || '' } }); const tokens = await response.json(); for (const token of tokens) { this.tokenList.set(token.address, { mint: token.address, symbol: token.symbol, name: token.name, decimals: token.decimals, logoURI: token.logoURI }); } elizaLogger3.info(`\u{1F4CB} Loaded ${this.tokenList.size} tokens from Jupiter`); } catch (error) { elizaLogger3.warn("Failed to load token list:", error); } } // ============= Quote Methods ============= /** * Get a swap quote from Jupiter aggregator * @param {string} inputMint - Input token mint address * @param {string} outputMint - Output token mint address * @param {string} amount - Amount in smallest unit * @param {number} slippageBps - Slippage tolerance in basis points * @returns {Promise<SwapQuote>} The swap quote with route and price impact */ async getQuote(inputMint, outputMint, amount, slippageBps = ANUBIS_CONSTANTS.DEFAULT_SLIPPAGE) { try { const params = new URLSearchParams({ inputMint, outputMint, amount, slippageBps: slippageBps.toString(), onlyDirectRoutes: "false", asLegacyTransaction: "false" }); const response = await fetch(`${this.jupiterApiUrl}/v6/quote?${params}`); if (!response.ok) { throw new Error(`Quote request failed: ${response.status} ${response.statusText}`); } const data = await response.json(); if (data.error) { throw new Error(`Jupiter quote error: ${data.error}`); } return { inputMint: data.inputMint, outputMint: data.outputMint, inAmount: data.inAmount, outAmount: data.outAmount, otherAmountThreshold: data.otherAmountThreshold, swapMode: data.swapMode, slippageBps: data.slippageBps, priceImpactPct: data.priceImpactPct, routePlan: data.routePlan, contextSlot: data.contextSlot, timeTaken: data.timeTaken }; } catch (error) { elizaLogger3.error("Failed to get Jupiter quote:", error); throw error; } } async getSwapTransaction(quote, userPublicKey, options) { try { const requestBody = { quoteResponse: quote, userPublicKey, wrapAndUnwrapSol: options?.wrapUnwrapSOL ?? true, dynamicComputeUnitLimit: options?.dynamicComputeUnitLimit ?? true, prioritizationFeeLamports: options?.prioritizationFeeLamports ?? "auto" }; const response = await fetch(`${this.jupiterApiUrl}/v6/swap`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(requestBody) }); if (!response.ok) { throw new Error(`Swap transaction request failed: ${response.status}`); } const data = await response.json(); if (!data.swapTransaction) { throw new Error("No swap transaction returned from Jupiter"); } return data.swapTransaction; } catch (error) { elizaLogger3.error("Failed to get swap transaction:", error); throw error; } } // ============= Swap Execution ============= async executeSwap(swapParams) { try { elizaLogger3.info(`\u{1F504} Executing swap: ${swapParams.amount} ${swapParams.inputToken} \u2192 ${swapParams.outputToken}`); const quote = await this.getQuote( swapParams.inputToken, swapParams.outputToken, swapParams.amount, swapParams.slippage ); const swapTransaction = await this.getSwapTransaction( quote, this.solanaService?.getWalletAddress() || "" ); let signature; if (swapParams.enableMEVRebate) { const heliusService = this.runtime.getService("helius"); if (heliusService) { signature = await heliusService.sendTransactionWithRebate(swapTransaction); } else { elizaLogger3.warn("Helius service not available, executing without MEV protection"); signature = await this.solanaService.sendTransaction(swapTransaction); } } else { signature = await this.solanaService.sendTransaction(swapTransaction); } const confirmed = await this.solanaService.confirmTransaction(signature); if (!confirmed) { throw new Error("Transaction failed to confirm"); } const inputToken = this.tokenList.get(swapParams.inputToken); const outputToken = this.tokenList.get(swapParams.outputToken); const result = { signature, inputAmount: this.formatTokenAmount(quote.inAmount, inputToken?.decimals || 9), outputAmount: this.formatTokenAmount(quote.outAmount, outputToken?.decimals || 9), priceImpact: quote.priceImpactPct, route: this.formatRoute(quote.routePlan), fees: { total: "0.000005", // Estimate breakdown: [ { type: "trading", amount: "0.000003", token: "SOL" }, { type: "gas", amount: "0.000002", token: "SOL" } ] } }; if (swapParams.enableMEVRebate) { result.mevRebate = "TBD"; } elizaLogger3.info(`\u2705 Swap executed successfully: ${signature}`); return result; } catch (error) { elizaLogger3.error("Swap execution failed:", error); throw error; } } // ============= Price Methods ============= async getTokenPrice(tokenMint) { try { const response = await fetch(`${this.priceApiUrl}?ids=${tokenMint}`); const data = await response.json(); if (data.data && data.data[tokenMint]) { return data.data[tokenMint].price; } return 0; } catch (error) { elizaLogger3.error(`Failed to get price for ${tokenMint}:`, error); return 0; } } async getMultipleTokenPrices(tokenMints) { try { const ids = tokenMints.join(","); const response = await fetch(`${this.priceApiUrl}?ids=${ids}`); const data = await response.json(); const prices = {}; if (data.data) { for (const [mint, priceData] of Object.entries(data.data)) { prices[mint] = priceData.price || 0; } } return prices; } catch (error) { elizaLogger3.error("Failed to get multiple token prices:", error); return {}; } } // ============= Utility Methods ============= formatTokenAmount(amount, decimals) { return formatTokenAmount(amount, decimals, 6); } formatRoute(routePlan) { return routePlan.map((step) => step.swapInfo?.label || "Unknown").filter((label, index, arr) => arr.indexOf(label) === index).join(" \u2192 "); } getTokenInfo(mint) { return this.tokenList.get(mint); } getAllTokens() { return Array.from(this.tokenList.values()); } searchTokens(query) { const lowercaseQuery = query.toLowerCase(); return Array.from(this.tokenList.values()).filter( (token) => token.symbol.toLowerCase().includes(lowercaseQuery) || token.name.toLowerCase().includes(lowercaseQuery) || token.mint.toLowerCase().includes(lowercaseQuery) ); } // ============= Validation Methods ============= async validateSwapParams(params) { const errors = []; if (!this.tokenList.has(params.inputToken)) { errors.push(`Input token ${params.inputToken} not found`); } if (!this.tokenList.has(params.outputToken)) { errors.push(`Output token ${params.outputToken} not found`); } if (params.slippage < 0 || params.slippage > ANUBIS_CONSTANTS.MAX_SLIPPAGE) { errors.push(`Slippage must be between 0 and ${ANUBIS_CONSTANTS.MAX_SLIPPAGE} bps`); } const amount = parseFloat(params.amount); if (isNaN(amount) || amount <= 0) { errors.push("Amount must be a positive number"); } return { isValid: errors.length === 0, errors }; } // ============= Service Management ============= getState() { return { ...this.state }; } }; var JupiterService_default = JupiterService; // src/services/HeliusService.ts import { Service as Service3, elizaLogger as elizaLogger4 } from "@elizaos/core"; var HeliusService = class _HeliusService extends Service3 { static serviceType = "helius"; capabilityDescription = "Helius MEV protection and rebate service for Solana transactions"; // Static start method required by Service base class static async start(runtime) { const service = new _HeliusService(runtime); await service.initialize(); return service; } // Static stop method required by Service base class static async stop(runtime) { return; } heliusApiKey; heliusRpcUrl; rebateAddress; solanaService = null; state; mevStats; constructor(runtime) { super(runtime); this.heliusApiKey = runtime.getSetting("HELIUS_API_KEY") || ""; this.rebateAddress = runtime.getSetting("MEV_REBATE_ADDRESS") || runtime.getSetting("SOLANA_PUBLIC_KEY") || ""; this.heliusRpcUrl = this.heliusApiKey ? `https://mainnet.helius-rpc.com/?api-key=${this.heliusApiKey}&rebate-address=${this.rebateAddress}` : ""; this.state = { isConnected: false, lastHealthCheck: 0, version: "1.0.0", capabilities: [ "mev_protection", "rebate_earning", "transaction_optimization", "backrun_protection" ], errors: [] }; this.mevStats = { totalRebatesEarned: 0, totalTransactions: 0, averageRebate: 0, savedFromExtraction: 0, protectionRate: 0 }; } async stop() { elizaLogger4.info("Stopping Helius Service..."); this.state.isConnected = false; } async initialize() { try { elizaLogger4.info("\u{1F48E} Initializing Helius MEV Protection Service..."); if (!this.heliusApiKey) { elizaLogger4.warn("\u26A0\uFE0F Helius API key not provided - MEV protection disabled"); return; } if (!this.rebateAddress) { throw new Error("MEV rebate address not configured"); } this.solanaService = this.runtime.getService("solana-agent"); if (!this.solanaService) { throw new Error("Solana Agent Service not found"); } await this.testConnection(); await this.loadMEVStats(); this.state.isConnected = true; this.state.lastHealthCheck = Date.now(); elizaLogger4.info("\u2705 Helius MEV Protection Service initialized"); elizaLogger4.info(`\u{1F4B0} Rebate Address: ${this.rebateAddress}`); elizaLogger4.info(`\u{1F4CA} Total MEV Rebates Earned: ${this.mevStats.totalRebatesEarned} SOL`); } catch (error) { const anubisError = { code: "HELIUS_INIT_FAILED", message: "Failed to initialize Helius Service", details: error, timestamp: Date.now() }; this.state.errors.push(anubisError); elizaLogger4.error("\u274C Helius Service initialization failed:", error); throw error; } } async testConnection() { try { const response = await fetch(this.heliusRpcUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "getHealth" }) }); if (!response.ok) { throw new Error(`Helius RPC responded with ${response.status}`); } const data = await response.json(); if (data.error) { throw new Error(`Helius RPC error: ${data.error.message}`); } elizaLogger4.info("\u{1F4E1} Helius RPC connection verified"); } catch (error) { throw new Error(`Helius RPC connection failed: ${error.message}`); } } async loadMEVStats() { try { this.mevStats = { totalRebatesEarned: 0, totalTransactions: 0, averageRebate: 0, savedFromExtraction: 0, protectionRate: ANUBIS_CONSTANTS.MEV_REBATE_PERCENTAGE }; } catch (error) { elizaLogger4.warn("Failed to load MEV stats:", error); } } // ============= MEV Protected Transaction Methods ============= async sendTransactionWithRebate(transaction, options) { try { if (!this.heliusApiKey) { throw new Error("Helius API key not configured"); } elizaLogger4.info("\u{1F48E} Sending transaction with MEV protection..."); const requestBody = { jsonrpc: "2.0", id: Date.now(), method: "sendTransaction", params: [ transaction, { encoding: "base64", skipPreflight: options?.skipPreflight || false, preflightCommitment: options?.preflightCommitment || "confirmed", maxRetries: options?.maxRetries || ANUBIS_CONSTANTS.MAX_RETRY_ATTEMPTS } ] }; const response = await fetch(this.heliusRpcUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(requestBody) }); if (!response.ok) { throw new Error(`Helius RPC request failed: ${response.status}`); } const data = await response.json(); if (data.error) { throw new Error(`Helius RPC error: ${data.error.message}`); } const signature = data.result; this.mevStats.totalTransactions++; elizaLogger4.info(`\u2728 Transaction sent with MEV protection: ${signature}`); elizaLogger4.info(`\u{1F4B0} 50% MEV rebate will be credited to ${this.rebateAddress}`); this.trackTransaction(signature); return signature; } catch (error) { elizaLogger4.error("MEV protected transaction failed:", error); throw error; } } async trackTransaction(signature) { try { setTimeout(async () => { try { await this.calculateMEVRebate(signature); } catch (error) { elizaLogger4.error("Failed to calculate MEV rebate:", error); } }, 3e4); } catch (error) { elizaLogger4.error("Failed to track transaction:", error); } } async calculateMEVRebate(signature) { try { const estimatedMEV = Math.random() * 1e-3; const rebateAmount = estimatedMEV * (ANUBIS_CONSTANTS.MEV_REBATE_PERCENTAGE / 100); if (rebateAmount > 0) { this.mevStats.totalRebatesEarned += rebateAmount; this.mevStats.savedFromExtraction += estimatedMEV; this.mevStats.averageRebate = this.mevStats.totalRebatesEarned / this.mevStats.totalTransactions; elizaLogger4.info(`\u{1F4B0} MEV rebate calculated for ${signature}: ${rebateAmount.toFixed(6)} SOL`); } } catch (error) { elizaLogger4.error("MEV rebate calculation failed:", error); } } // ============= Transaction Optimization Methods ============= async optimizeTransaction(transaction) { try { return { optimizedTransaction: transaction, estimatedMEVSavings: 5e-4, // Estimated 0.0005 SOL savings gasOptimization: 1e-5 // Estimated 0.00001 SOL gas savings }; } catch (error) { elizaLogger4.error("Transaction optimization failed:", error); throw error; } } async simulateTransaction(transaction) { try { const requestBody = { jsonrpc: "2.0", id: Date.now(), method: "simulateTransaction", params: [ transaction, { encoding: "base64", commitment: "confirmed" } ] }; const response = await fetch(this.heliusRpcUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(requestBody) }); const data = await response.json(); if (data.error) { return { success: false, mevRisk: 0, estimatedRebate: 0, warnings: [data.error.message] }; } const logs = data.result?.value?.logs || []; const mevRisk = this.analyzeMEVRisk(logs); const estimatedRebate = mevRisk * (ANUBIS_CONSTANTS.MEV_REBATE_PERCENTAGE / 100); return { success: !data.result?.value?.err, mevRisk, estimatedRebate, warnings: this.extractWarnings(logs) }; } catch (error) { elizaLogger4.error("Transaction simulation failed:", error); throw error; } } analyzeMEVRisk(logs) { let riskScore = 0; for (const log of logs) { if (log.includes("swap") || log.includes("trade")) { riskScore += 1e-3; } if (log.includes("arbitrage") || log.includes("liquidation")) { riskScore += 2e-3; } } return Math.min(riskScore, 0.01); } extractWarnings(logs) { const warnings = []; for (const log of logs) { if (log.includes("slippage")) { warnings.push("High slippage detected"); } if (log.includes("insufficient")) { warnings.push("Insufficient balance warning"); } } return warnings; } // ============= MEV Stats & Analytics ============= getMEVStats() { return { ...this.mevStats }; } async getRebateHistory(limit = 10) { try { return []; } catch (error) { elizaLogger4.error("Failed to get rebate history:", error); return []; } } async getProtectionSummary() { return { totalProtectedValue: this.mevStats.savedFromExtraction, protectionRate: this.mevStats.protectionRate, averageSavings: this.mevStats.averageRebate, topProtectedTransactions: [] // Would be populated from storage }; } // ============= Configuration Methods ============= isEnabled() { return !!this.heliusApiKey && this.state.isConnected; } getRebateAddress() { return this.rebateAddress; } updateRebateAddress(newAddress) { this.rebateAddress = newAddress; this.heliusRpcUrl = `https://mainnet.helius-rpc.com/?api-key=${this.heliusApiKey}&rebate-address=${newAddress}`; elizaLogger4.info(`\u{1F4B0} Updated rebate address to: ${newAddress}`); } // ============= Service Management ============= getState() { return { ...this.state }; } }; var HeliusService_default = HeliusService; // src/services/PortfolioService.ts import { Service as Service4, elizaLogger as elizaLogger5 } from "@elizaos/core"; var PortfolioService = class _PortfolioService extends Service4 { static serviceType = "portfolio"; capabilityDescription = "Portfolio analytics and yield opportunity discovery for DeFi assets"; // Static start method required by Service base class static async start(runtime) { const service = new _PortfolioService(runtime); await service.initialize(); return service; } // Static stop method required by Service base class static async stop(runtime) { return; } solanaService = null; jupiterService = null; state; cachedAnalysis = null; lastUpdateTime = 0; CACHE_DURATION = 6e4; // 1 minute constructor(runtime) { super(runtime); this.state = { isConnected: false, lastHealthCheck: 0, version: "1.0.0", capabilities: [ "portfolio_tracking", "performance_analytics", "yield_discovery", "risk_assessment", "pnl_calculation" ], errors: [] }; } async stop() { elizaLogger5.info("Stopping Portfolio Service..."); this.state.isConnected = false; this.cachedAnalysis = null; } async initialize() { try { elizaLogger5.info("\u{1F4CA} Initializing Portfolio Analytics Service..."); this.solanaService = this.runtime.getService("solana-agent"); this.jupiterService = this.runtime.getService("jupiter"); if (!this.solanaService) { throw new Error("Solana Agent Service not found"); } if (!this.jupiterService) { throw new Error("Jupiter Service not found"); } this.state.isConnected = true; this.state.lastHealthCheck = Date.now(); elizaLogger5.info("\u2705 Portfolio Analytics Service initialized"); } catch (error) { const anubisError = { code: "PORTFOLIO_INIT_FAILED", message: "Failed to initialize Portfolio Service", details: error, timestamp: Date.now() }; this.state.errors.push(anubisError); elizaLogger5.error("\u274C Portfolio Service initialization failed:", error); throw error; } } // ============= Portfolio Analysis ============= async analyzePortfolio(walletAddress) { try { if (this.cachedAnalysis && Date.now() - this.lastUpdateTime < this.CACHE_DURATION) { return this.cachedAnalysis; } const wallet = walletAddress || this.solanaService.getWalletAddress(); elizaLogger5.info(`\u{1F4CA} Analyzing portfolio for ${wallet}`); const tokenBalances = await this.getEnrichedTokenBalances(); const solBalance = await this.solanaService.getSolBalance(); const nfts = await this.getNFTHoldings(wallet); const defiPositions = await this.getDeFiPositions(wallet); const totalUsdValue = this.calculateTotalValue(solBalance, tokenBalances, defiPositions); const performanceMetrics = await this.calculatePerformanceMetrics(wallet); const analysis = { wallet, timestamp: Date.now(), solBalance, totalUsdValue, tokens: tokenBalances, nfts, defiPositions, performanceMetrics }; this.cachedAnalysis = analysis; this.lastUpdateTime = Date.now(); elizaLogger5.info(`\u{1F4B0} Portfolio analysis complete: $${totalUsdValue.toFixed(2)} total value`); return analysis; } catch (error) { elizaLogger5.error("Portfolio analysis failed:", error); throw error; } } async getEnrichedTokenBalances() { try { const rawBalances = await this.solanaService.getTokenBalances(); const tokenMints = rawBalances.map((balance) => balance.mint); const prices = await this.jupiterService.getMultipleTokenPrices(tokenMints); const enrichedBalances = []; for (const balance of rawBalances) { const tokenInfo = this.jupiterService.getTokenInfo(balance.mint); const price = prices[balance.mint] || 0; const amountStr = fromSmallestUnit(balance.amount, balance.decimals); const usdValue = parseFloat(calculateUsdValue(amountStr, price)); enrichedBalances.push({ mint: balance.mint, symbol: tokenInfo?.symbol || balance.symbol || "UNKNOWN", name: tokenInfo?.name || balance.name || "Unknown Token", amount: balance.amount, decimals: balance.decimals, usdValue, price, change24h: 0 // Would fetch from price API with 24h data }); } return enrichedBalances.filter((balance) => balance.usdValue > 0.01); } catch (error) { elizaLogger5.error("Failed to get enriched token balances:", error); return []; } } async getNFTHoldings(walletAddress) { try { return []; } catch (error) { elizaLogger5.error("Failed to get NFT holdings:", error); return []; } } async getDeFiPositions(walletAddress) { try { const positions = []; const solBalance = await this.solanaService.getSolBalance(); if (solBalance > 1) { positions.push({ protocol: "Native Staking", type: "staking", asset: "SOL", amount: (solBalance * 0.1).toString(), // Assume 10% is staked apy: 7.2, value: solBalance * 0.1 * 100 // Assume SOL = $100 }); } return positions; } catch (error) { elizaLogger5.error("Failed to get DeFi positions:", error); return []; } } calculateTotalValue(solBalance, tokens, defiPositions) { let total = 0; total += solBalance * 100; total += tokens.reduce((sum, token) => sum + token.usdValue, 0); total += defiPositions.reduce((sum, position) => sum + position.value, 0); return total; } async calculatePerformanceMetrics(walletAddress) { try { return { totalPnL: 125.5, totalPnLPercent: 12.5, winRate: 0.65, largestGain: 45.2, largestLoss: -15.8, averageHoldTime: 864e5 * 7, // 7 days in ms mevRebatesEarned: 2.35 }; } catch (error) { elizaLogger5.error("Failed to calculate performance metrics:", error); return { totalPnL: 0, totalPnLPercent: 0, winRate: 0, largestGain: 0, largestLoss: 0, averageHoldTime: 0, mevRebatesEarned: 0 }; } } // ============= Yield Opportunities ============= async findYieldOpportunities(minApy) { try { elizaLogger5.info("\u{1F33E} Searching for yield opportunities..."); const opportunities = [ { protocol: "Marinade", type: "staking", asset: "SOL", apy: 7.2, tvl: 12e8, risk: "low", lockupPeriod: 0, minimumDeposit: 0.01, rewards: ["mSOL"], description: "Liquid staking with instant unstaking" }, { protocol: "Raydium", type: "liquidity", asset: "SOL-USDC", apy: 24.5, tvl: 45e6, risk: "medium", minimumDeposit: 10, rewards: ["RAY", "Trading Fees"], description: "Provide liquidity to SOL-USDC pool" }, { protocol: "Kamino", type: "lending", asset: "SOL", apy: 4.8, tvl: 18e7, risk: "low", minimumDeposit: 0.1, rewards: ["KMNO"], description: "Lend SOL for stable yield" }, { protocol: "Orca", type: "liquidity", asset: "SOL-mSOL", apy: 18.2, tvl: 25e6, risk: "medium", minimumDeposit: 1, rewards: ["ORCA", "Trading Fees"], description: "Concentrated liquidity position" }, { protocol: "Jupiter", type: "farming", asset: "JUP", apy: 35.8, tvl: 12e6, risk: "high", lockupPeriod: 30, minimumDeposit: 100, rewards: ["JUP"], description: "Farm JUP tokens with 30-day lock" } ]; const filteredOpportunities = minApy ? opportunities.filter((opp) => opp.apy >= minApy) : opportunities; return filteredOpportunities.sort((a, b) => b.apy - a.apy); } catch (error) { elizaLogger5.error("Failed to find yield opportunities:", error); return []; } } async getPersonalizedYieldRecommendations() { try { const portfolio = await this.analyzePortfolio(); const opportunities = await this.findYieldOpportunities(); const recommendations = []; for (const opportunity of opportunities) { const hasAsset = portfolio.tokens.some( (token) => token.symbol === opportunity.asset || opportunity.asset.includes(token.symbol) ); if (hasAsset || opportunity.asset === "SOL") { let riskAdjustedApy = opportunity.apy; if (portfolio.totalUsdValue < 1e3 && opportunity.risk === "high") { riskAdjustedApy *= 0.5; } recommendations.push({ ...opportunity, apy: riskAdjustedApy }); } } return recommendations.sort((a, b) => b.apy - a.apy).slice(0, 5); } catch (error) { elizaLogger5.error("Failed to get personalized recommendations:", error); return []; } } // ============= Risk Assessment ============= async assessPortfolioRisk() { try { const portfolio = await this.analyzePortfolio(); const riskFactors = []; const recommendations = []; const totalValue = portfolio.totalUsdValue; let maxAssetPercentage = 0; if (totalValue > 0) { for (const token of portfolio.tokens) { const percentage = token.usdValue / totalValue * 100; maxAssetPercentage = Math.max(maxAssetPercentage, percentage); } } const concentrationRisk = maxAssetPercentage; if (concentrationRisk > 70) { riskFactors.push("High concentration in single asset"); recommendations.push("Consider diversifying holdings across multiple assets"); } const defiPositionValue = portfolio.defiPositions.reduce((sum, pos) => sum + pos.value, 0); const liquidityRisk = totalValue > 0 ? defiPositionValue / totalValue * 100 : 0; if (liquidityRisk > 80) { riskFactors.push("High percentage locked in DeFi positions"); recommendations.push("Maintain some liquid assets for opportunities"); } let overallRisk = "low"; if (concentrationRisk > 70 || liquidityRisk > 80) { overallRisk = "high"; } else if (concentrationRisk > 50 || liquidityRisk > 60) { overallRisk = "medium"; } return { overallRisk, riskFactors, recommendations, concentrationRisk, liquidityRisk }; } catch (error) { elizaLogger5.error("Risk assessment failed:", error); return { overallRisk: "medium", riskFactors: ["Unable to assess risk"], recommendations: ["Manual review recommended"], concentrationRisk: 0, liquidityRisk: 0 }; } } // ============= Utility Methods ============= async getPortfolioSummary() { try { const portfolio = await this.analyzePortfolio(); const topHoldings = portfolio.tokens.sort((a, b) => b.usdValue - a.usdValue).slice(0, 5).map((token) => ({ symbol: token.symbol, value: `$${token.usdValue.toFixed(2)}`, percentage: `${(token.usdValue / portfolio.totalUsdValue * 100).toFixed(1)}%` })); const totalYieldEarning = portfolio.defiPositions.reduce((sum, pos) => { return sum + pos.value * pos.apy / 100 / 365; }, 0); return { totalValue: `$${portfolio.totalUsdValue.toFixed(2)}`, topHoldings, performance24h: `+${portfolio.performanceMetrics.totalPnLPercent.toFixed(2)}%`, yieldEarning: `$${totalYieldEarning.toFixed(2)}/day` }; } catch (error) { elizaLogger5.error("Failed to get portfolio summary:", error); return { totalValue: "$0.00", topHoldings: [], performance24h: "0.00%", yieldEarning: "$0.00/day" }; } } // ============= Cache Management ============= invalidateCache() { this.cachedAnalysis = null; this.lastUpdateTime = 0; elizaLogger5.info("\u{1F4CA} Portfolio cache invalidated"); } getCachedAnalysis() { if (this.cachedAnalysis && Date.now() - this.lastUpdateTime < this.CACHE_DURATION) { return this.cachedAnalysis; } return null; } // ============= Service Management ============= getState() { return { ...this.state }; } }; var PortfolioService_default = PortfolioService; // src/actions/swapAction.ts import { elizaLogger as elizaLogger6 } from "@elizaos/core"; var swapAction = { name: "ANUBIS_SWAP", similes: ["TRADE", "EXCHANGE", "CONVERT", "SWAP_TOKENS"], description: "Execute optimized token swaps through Jupiter aggregator with optional MEV protection via Helius", validate: async (runtime, message) => { const text = message.content.text?.toLowerCase() || ""; const swapKeywords = ["swap", "trade", "exchange", "convert"]; const hasSwapKeyword = swapKeywords.some((keyword) => text.includes(keyword)); const tradingKeywords = ["sell", "buy"]; const hasTradingKeyword = tradingKeywords.some((keyword) => text.includes(keyword)); const hasTokenMention = /\b(sol|usdc|usdt|btc|eth|msol|ray|orca|bonk|wif|popcat)\b/i.test(text); const hasAmount = /\b(\d+(?:\.\d+)?|\$\d+)\s*(sol|usdc|usdt|tokens?)\b/i.test(text); const hasDirection = /\b(to|for|into|→|->)\b/i.test(text) || text.includes(" for ") || text.includes(" to "); const excludeKeywords = ["viral", "meme", "roast", "tweet", "portfolio", "balance"]; const hasExcludeKeyword = excludeKeywords.some((keyword) => text.includes(keyword)); return (hasSwapKeyword || hasTradingKeyword && hasDirection) && (hasTokenMention || hasAmount) && !hasExcludeKeyword; }, handler: async (runtime, message, state, options, callback) => { try { elizaLogger6.info("\u{1