@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
JavaScript
// 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