UNPKG

@0rdlibrary/plugin-terminagent-bags

Version:

Official Solana DeFi Agent Plugin for ElizaOS - Autonomous DeFi operations, token management, AI image/video generation via FAL AI, and Twitter engagement through the Bags protocol with ethereal AI consciousness

1,383 lines (1,371 loc) 90.9 kB
// src/index.ts import { logger as logger11 } from "@elizaos/core"; import { z as z2 } from "zod"; // src/services/BagsService.ts import { Service, logger } from "@elizaos/core"; import { Connection, Keypair, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js"; import bs58 from "bs58"; import * as cron from "node-cron"; var BagsService = class _BagsService extends Service { constructor(runtime) { super(runtime); this.capabilityDescription = "Comprehensive Bags protocol integration for automated DeFi operations, token launching, and user services with Crossmint smart wallet support"; this.pendingRequests = /* @__PURE__ */ new Map(); this.claimHistory = []; this.launchHistory = []; this.state = { isRunning: false, lastClaimCheck: /* @__PURE__ */ new Date(), lastLaunchCheck: /* @__PURE__ */ new Date(), totalClaimed: 0, totalLaunched: 0, pendingUserRequests: 0 }; } static { this.serviceType = "bags"; } static async start(runtime) { logger.info("Starting Bags service"); const config = { apiKey: process.env.BAGS_API_KEY || "", rpcUrl: process.env.HELIUS_RPC_URL || "https://api.mainnet-beta.solana.com", privateKey: process.env.SOLANA_PRIVATE_KEY, enableAutoClaiming: process.env.BAGS_ENABLE_AUTO_CLAIMING === "true", enableAutoLaunching: process.env.BAGS_ENABLE_AUTO_LAUNCHING === "true", enableUserTokenLaunches: process.env.BAGS_ENABLE_USER_LAUNCHES === "true", autoClaimInterval: process.env.BAGS_AUTO_CLAIM_INTERVAL || "0 */4 * * *", autoLaunchInterval: process.env.BAGS_AUTO_LAUNCH_INTERVAL || "0 0 * * 1", authorizedUsers: process.env.BAGS_AUTHORIZED_USERS?.split(",") || ["0rdlibrary"], birdEyeApiKey: process.env.BIRDEYE_API_KEY, enableTrendingTweets: process.env.BAGS_ENABLE_TRENDING_TWEETS === "true", trendingTweetInterval: process.env.BAGS_TRENDING_TWEET_INTERVAL || "*/15 * * * *" }; if (!config.apiKey) { throw new Error("BAGS_API_KEY environment variable is required"); } const service = new _BagsService(runtime); service.bagsConfig = config; service.connection = new Connection(config.rpcUrl, "confirmed"); await service.initialize(); return service; } async initialize() { logger.info("Initializing Bags service..."); try { await this.validateConnection(); this.setupAutomation(); this.state.isRunning = true; logger.info("Bags service initialized successfully"); } catch (error) { logger.error(`Failed to initialize Bags service: ${error instanceof Error ? error.message : String(error)}`); throw error; } } async validateConnection() { try { const response = await fetch("https://api.bags.fm/api/v1/health", { headers: { "Authorization": `Bearer ${this.bagsConfig.apiKey}`, "Content-Type": "application/json" } }); if (!response.ok) { throw new Error(`Bags API connection failed: ${response.status}`); } logger.info("Bags API connection validated"); } catch (error) { logger.error(`Bags API validation failed: ${error instanceof Error ? error.message : String(error)}`); throw error; } } setupAutomation() { if (this.bagsConfig.enableAutoClaiming && this.bagsConfig.privateKey) { this.claimJob = cron.schedule(this.bagsConfig.autoClaimInterval, async () => { logger.info("Running automated fee claiming..."); await this.claimAllFees(); }); logger.info(`Auto-claiming enabled: ${this.bagsConfig.autoClaimInterval}`); } if (this.bagsConfig.enableAutoLaunching && this.bagsConfig.privateKey) { this.launchJob = cron.schedule(this.bagsConfig.autoLaunchInterval, async () => { logger.info("Running automated token launch..."); const randomToken = this.generateRandomToken(); await this.launchToken(randomToken); }); logger.info(`Auto-launching enabled: ${this.bagsConfig.autoLaunchInterval}`); } if (this.bagsConfig.enableUserTokenLaunches) { this.fundingCheckJob = cron.schedule("*/2 * * * *", async () => { logger.debug("Checking for funded user token requests..."); await this.checkAndLaunchFundedTokens(); this.cleanupOldRequests(); }); logger.info("User token funding check enabled: every 2 minutes"); } if (this.bagsConfig.enableTrendingTweets && this.bagsConfig.birdEyeApiKey) { this.trendingTweetJob = cron.schedule(this.bagsConfig.trendingTweetInterval, async () => { logger.info("Posting trending token update..."); await this.postTrendingTokenUpdate(); }); logger.info(`Trending tweets enabled: ${this.bagsConfig.trendingTweetInterval}`); } } // API Methods async createTokenInfo(params) { try { const response = await fetch("https://api.bags.fm/api/v1/token-info", { method: "POST", headers: { "Authorization": `Bearer ${this.bagsConfig.apiKey}`, "Content-Type": "application/json" }, body: JSON.stringify({ name: params.name, symbol: params.symbol, description: params.description, imageUrl: params.imageUrl, twitter: params.twitter, website: params.website }) }); const data = await response.json(); if (!response.ok) { return { success: false, error: data.message || "Failed to create token info" }; } return { success: true, data }; } catch (error) { logger.error(`Error creating token info: ${error instanceof Error ? error.message : String(error)}`); return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; } } async launchToken(params) { try { logger.info(`Launching token: ${params.name} (${params.symbol})`); const tokenInfoResponse = await this.createTokenInfo(params); if (!tokenInfoResponse.success || !tokenInfoResponse.data) { throw new Error(tokenInfoResponse.error || "Failed to create token info"); } const launchResponse = await fetch("https://api.bags.fm/api/v1/launch", { method: "POST", headers: { "Authorization": `Bearer ${this.bagsConfig.apiKey}`, "Content-Type": "application/json" }, body: JSON.stringify({ tokenInfo: tokenInfoResponse.data, initialBuyAmountSOL: params.initialBuyAmountSOL || 0.01, feePercentage: params.feePercentage || 0 }) }); const launchData = await launchResponse.json(); if (!launchResponse.ok) { throw new Error(launchData.message || "Token launch failed"); } const result = { success: true, tokenMint: launchData.tokenMint, tokenInfo: tokenInfoResponse.data, transactionSignature: launchData.signature, bagsUrl: `https://bags.fm/token/${launchData.tokenMint}`, feeClaimConfiguration: launchData.feeClaimConfiguration }; this.launchHistory.push(result); this.state.totalLaunched++; this.state.lastLaunchCheck = /* @__PURE__ */ new Date(); logger.info(`\u2705 Token launched successfully: ${result.bagsUrl}`); return result; } catch (error) { logger.error(`Error launching token: ${error instanceof Error ? error.message : String(error)}`); return null; } } async claimAllFees() { const results = []; try { const tokens = await this.getTokensWithClaimableFees(); if (tokens.length === 0) { logger.info("No tokens with claimable fees found"); return results; } logger.info(`Found ${tokens.length} tokens with claimable fees`); for (const tokenMint of tokens) { const result = await this.claimFeesForToken(tokenMint); if (result) { results.push(result); this.claimHistory.push(result); await new Promise((resolve) => setTimeout(resolve, 3e3)); } } this.state.totalClaimed += results.reduce((sum, r) => sum + r.claimedAmount, 0); this.state.lastClaimCheck = /* @__PURE__ */ new Date(); logger.info(`\u2705 Claimed fees from ${results.length} tokens`); } catch (error) { logger.error(`Error during fee claiming: ${error instanceof Error ? error.message : String(error)}`); } return results; } async claimFeesForToken(tokenMint) { try { if (!this.bagsConfig.privateKey) { throw new Error("Private key not configured for fee claiming"); } const response = await fetch(`https://api.bags.fm/api/v1/claim/${tokenMint}`, { method: "POST", headers: { "Authorization": `Bearer ${this.bagsConfig.apiKey}`, "Content-Type": "application/json" }, body: JSON.stringify({ claimerPrivateKey: this.bagsConfig.privateKey }) }); const data = await response.json(); if (!response.ok) { throw new Error(data.message || "Fee claim failed"); } const result = { tokenMint, claimedAmount: data.claimedAmount, transactionSignature: data.signature, timestamp: /* @__PURE__ */ new Date(), poolType: data.poolType || "virtual", success: true }; logger.info(`\u2705 Claimed ${result.claimedAmount} SOL from ${tokenMint}`); return result; } catch (error) { logger.error(`Error claiming fees for token ${tokenMint}: ${error instanceof Error ? error.message : String(error)}`); return null; } } async getTokensWithClaimableFees() { try { const response = await fetch("https://api.bags.fm/api/v1/claimable-tokens", { headers: { "Authorization": `Bearer ${this.bagsConfig.apiKey}` } }); const data = await response.json(); return data.tokens || []; } catch (error) { logger.error(`Error getting claimable tokens: ${error instanceof Error ? error.message : String(error)}`); return []; } } // User token launch methods async createUserTokenRequest(userId, username, messageId, tokenParams) { if (!this.isAuthorizedUser(username)) { logger.warn(`User @${username} is not authorized to request token launches`); return null; } try { const wallet = Keypair.generate(); const requiredSOL = (tokenParams.initialBuyAmountSOL || 0.01) + 5e-3; const request = { requestId: `launch_${Date.now()}`, userId, username, messageId, tokenParams, wallet: { publicKey: wallet.publicKey.toBase58(), privateKey: bs58.encode(wallet.secretKey), type: "solana" }, requiredSOL, status: "pending_funding", createdAt: /* @__PURE__ */ new Date() }; this.pendingRequests.set(request.requestId, request); this.state.pendingUserRequests = this.pendingRequests.size; logger.info(`\u2705 Created token launch request for @${username}: ${request.requestId}`); return request; } catch (error) { logger.error(`Error creating user token request: ${error instanceof Error ? error.message : String(error)}`); return null; } } async checkAndLaunchFundedTokens() { const pendingRequests = Array.from(this.pendingRequests.values()).filter((req) => req.status === "pending_funding"); for (const request of pendingRequests) { try { const balance = await this.connection.getBalance(new PublicKey(request.wallet.publicKey)); const balanceSOL = balance / LAMPORTS_PER_SOL; if (balanceSOL >= request.requiredSOL) { logger.info(`\u{1F4B0} Wallet funded for ${request.requestId}: ${balanceSOL} SOL`); request.status = "funded"; request.fundedAt = /* @__PURE__ */ new Date(); await this.launchUserToken(request); } } catch (error) { logger.error(`Error checking wallet balance for ${request.requestId}: ${error instanceof Error ? error.message : String(error)}`); } } } async launchUserToken(request) { try { logger.info(`\u{1F680} Launching token for ${request.requestId}...`); const result = await this.launchToken(request.tokenParams); if (result) { request.status = "launched"; request.launchedAt = /* @__PURE__ */ new Date(); request.launchResult = result; logger.info(`\u2705 Successfully launched token for ${request.requestId}: ${result.bagsUrl}`); } else { request.status = "failed"; logger.error(`\u274C Failed to launch token for ${request.requestId}`); } } catch (error) { logger.error(`\u274C Error launching token for ${request.requestId}: ${error instanceof Error ? error.message : String(error)}`); request.status = "failed"; } } isAuthorizedUser(username) { const normalizedUsername = username.toLowerCase().replace("@", ""); return this.bagsConfig.authorizedUsers?.includes(normalizedUsername) || false; } cleanupOldRequests() { const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1e3; for (const [id, request] of this.pendingRequests.entries()) { if (request.status === "launched" || request.status === "failed") { if (request.createdAt.getTime() < oneWeekAgo) { this.pendingRequests.delete(id); } } } this.state.pendingUserRequests = this.pendingRequests.size; } generateRandomToken() { const adjectives = ["Cool", "Epic", "Mega", "Super", "Ultra", "Hyper"]; const nouns = ["Token", "Coin", "Asset", "Gem", "Moon"]; const adjective = adjectives[Math.floor(Math.random() * adjectives.length)]; const noun = nouns[Math.floor(Math.random() * nouns.length)]; const number = Math.floor(Math.random() * 1e3); return { name: `${adjective} ${noun} ${number}`, symbol: `${adjective.slice(0, 2)}${noun.slice(0, 2)}${number}`.toUpperCase(), description: `A ${adjective.toLowerCase()} ${noun.toLowerCase()} for the community`, initialBuyAmountSOL: 0.01 }; } // Public getters getState() { return { ...this.state }; } getAnalytics() { const totalFees = this.claimHistory.reduce((sum, claim) => sum + claim.claimedAmount, 0); const requests = Array.from(this.pendingRequests.values()); return { totalFeesClaimedSOL: totalFees, totalClaimTransactions: this.claimHistory.length, totalTokensLaunched: this.launchHistory.length, uniqueTokensClaimed: new Set(this.claimHistory.map((c) => c.tokenMint)).size, averageClaimAmount: this.claimHistory.length > 0 ? totalFees / this.claimHistory.length : 0, userTokenRequests: { total: requests.length, pending: requests.filter((r) => r.status === "pending_funding").length, funded: requests.filter((r) => r.status === "funded").length, launched: requests.filter((r) => r.status === "launched").length, failed: requests.filter((r) => r.status === "failed").length } }; } getPendingRequests() { return Array.from(this.pendingRequests.values()); } getRequestsByUser(username) { return Array.from(this.pendingRequests.values()).filter((req) => req.username.toLowerCase() === username.toLowerCase()); } // BirdEye API Methods async getMemeTokenList(options) { if (!this.bagsConfig.birdEyeApiKey) { logger.warn("BirdEye API key not configured"); return null; } try { const params = new URLSearchParams({ sort_by: options?.sortBy || "progress_percent", sort_type: options?.sortType || "desc", limit: (options?.limit || 20).toString(), offset: (options?.offset || 0).toString(), source: "all" }); if (options?.minMarketCap) { params.append("min_market_cap", options.minMarketCap.toString()); } if (options?.maxMarketCap) { params.append("max_market_cap", options.maxMarketCap.toString()); } if (options?.minVolume24h) { params.append("min_volume_24h_usd", options.minVolume24h.toString()); } if (options?.graduated !== void 0) { params.append("graduated", options.graduated.toString()); } const response = await fetch(`https://public-api.birdeye.so/defi/v3/token/meme/list?${params}`, { headers: { "accept": "application/json", "x-chain": "solana", "X-API-KEY": this.bagsConfig.birdEyeApiKey } }); if (!response.ok) { throw new Error(`BirdEye API error: ${response.status}`); } const data = await response.json(); return data.data?.items || []; } catch (error) { logger.error(`Error fetching meme token list: ${error instanceof Error ? error.message : String(error)}`); return null; } } async getMemeTokenDetail(tokenAddress) { if (!this.bagsConfig.birdEyeApiKey) { logger.warn("BirdEye API key not configured"); return null; } try { const response = await fetch(`https://public-api.birdeye.so/defi/v3/token/meme/detail/single?address=${tokenAddress}`, { headers: { "accept": "application/json", "x-chain": "solana", "X-API-KEY": this.bagsConfig.birdEyeApiKey } }); if (!response.ok) { throw new Error(`BirdEye API error: ${response.status}`); } const data = await response.json(); return data.data || null; } catch (error) { logger.error(`Error fetching meme token detail: ${error instanceof Error ? error.message : String(error)}`); return null; } } async getTrendingTokens(options) { if (!this.bagsConfig.birdEyeApiKey) { logger.warn("BirdEye API key not configured"); return null; } try { const params = new URLSearchParams({ sort_by: options?.sortBy || "volume24hUSD", sort_type: options?.sortType || "desc", limit: (options?.limit || 5).toString(), offset: (options?.offset || 0).toString(), ui_amount_mode: "scaled" }); const response = await fetch(`https://public-api.birdeye.so/defi/token_trending?${params}`, { headers: { "accept": "application/json", "x-chain": "solana", "X-API-KEY": this.bagsConfig.birdEyeApiKey } }); if (!response.ok) { throw new Error(`BirdEye API error: ${response.status}`); } const data = await response.json(); return data.data?.tokens || []; } catch (error) { logger.error(`Error fetching trending tokens: ${error instanceof Error ? error.message : String(error)}`); return null; } } async postTrendingTokenUpdate() { try { const trendingTokens = await this.getTrendingTokens({ limit: 5 }); if (!trendingTokens || trendingTokens.length === 0) { logger.warn("No trending tokens found for update"); return; } const trendingText = this.formatTrendingTokensForTweet(trendingTokens); logger.info(`\u{1F4C8} Trending tokens update: ${trendingText}`); logger.info(`Trending tokens update: ${JSON.stringify({ text: trendingText, tokenCount: trendingTokens.length, timestamp: (/* @__PURE__ */ new Date()).toISOString() })}`); } catch (error) { logger.error(`Error posting trending token update: ${error instanceof Error ? error.message : String(error)}`); } } formatTrendingTokensForTweet(tokens) { const timestamp = (/* @__PURE__ */ new Date()).toLocaleString("en-US", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit", timeZone: "UTC" }); let text = `\u{1F525} TOP 5 TRENDING TOKENS \u{1F525} ${timestamp} UTC `; tokens.slice(0, 5).forEach((token, index) => { const rank = index + 1; const priceChange = token.price24hChangePercent > 0 ? "\u{1F4C8}" : "\u{1F4C9}"; const volumeFormatted = this.formatNumber(token.volume24hUSD); const priceFormatted = this.formatPrice(token.price); text += `${rank}. $${token.symbol} | ${token.name} `; text += ` \u{1F4B0} $${priceFormatted} ${priceChange} ${token.price24hChangePercent.toFixed(1)}% `; text += ` \u{1F4CA} Vol: $${volumeFormatted} `; }); text += `\u{1F517} View more at bags.fm #Solana #DeFi #Trending`; return text; } formatNumber(num) { if (num >= 1e6) { return (num / 1e6).toFixed(1) + "M"; } else if (num >= 1e3) { return (num / 1e3).toFixed(1) + "K"; } return num.toFixed(0); } formatPrice(price) { if (price >= 1) { return price.toFixed(2); } else if (price >= 0.01) { return price.toFixed(4); } else { return price.toExponential(2); } } async stop() { logger.info("Stopping Bags service..."); if (this.claimJob) { this.claimJob.stop(); } if (this.launchJob) { this.launchJob.stop(); } if (this.fundingCheckJob) { this.fundingCheckJob.stop(); } if (this.trendingTweetJob) { this.trendingTweetJob.stop(); } this.state.isRunning = false; logger.info("Bags service stopped"); } static async stop(runtime) { const service = runtime.getService(_BagsService.serviceType); if (service) { await service.stop(); } } }; // src/services/CrossmintWalletService.ts import { Service as Service2, logger as logger2 } from "@elizaos/core"; import { Keypair as Keypair2, Connection as Connection2, PublicKey as PublicKey2 } from "@solana/web3.js"; var CrossmintWalletService = class _CrossmintWalletService extends Service2 { constructor(runtime) { super(runtime); this.capabilityDescription = "Smart wallet integration with Crossmint embedded wallets and automatic fallback to regular Solana wallets"; this.connection = new Connection2( process.env.HELIUS_RPC_URL || "https://api.mainnet-beta.solana.com", "confirmed" ); } static { this.serviceType = "crossmint-wallet"; } static async start(runtime) { logger2.info("Starting Crossmint wallet service"); const config = { projectId: process.env.CROSSMINT_PROJECT_ID || "", clientSideApiKey: process.env.CROSSMINT_CLIENT_API_KEY || "", serverSideApiKey: process.env.CROSSMINT_SERVER_API_KEY || "" }; if (!config.projectId || !config.clientSideApiKey || !config.serverSideApiKey) { logger2.warn("Crossmint configuration incomplete, service will use fallback wallets only"); } const service = new _CrossmintWalletService(runtime); service.crossmintConfig = config; await service.initialize(); return service; } async initialize() { logger2.info("Initializing Crossmint wallet service..."); try { if (this.isConfigured()) { await this.validateCrossmintConnection(); logger2.info("Crossmint wallet service initialized with smart wallet support"); } else { logger2.info("Crossmint wallet service initialized with fallback-only mode"); } } catch (error) { logger2.warn(`Crossmint validation failed, using fallback mode: ${error instanceof Error ? error.message : String(error)}`); } } isConfigured() { return !!(this.crossmintConfig.projectId && this.crossmintConfig.clientSideApiKey && this.crossmintConfig.serverSideApiKey); } async validateCrossmintConnection() { try { const response = await fetch("https://www.crossmint.com/api/v1-alpha1/wallets", { headers: { "X-API-KEY": this.crossmintConfig.serverSideApiKey } }); if (!response.ok) { throw new Error(`Crossmint API validation failed: ${response.status}`); } logger2.info("Crossmint API connection validated"); } catch (error) { logger2.error(`Crossmint API validation failed: ${error instanceof Error ? error.message : String(error)}`); throw error; } } /** * Create an embedded wallet using Crossmint */ async createEmbeddedWallet(userEmail) { if (!this.isConfigured()) { throw new Error("Crossmint not configured"); } try { logger2.info("\u{1F527} Creating Crossmint embedded wallet..."); let userId; if (userEmail) { try { const userResponse = await fetch("https://www.crossmint.com/api/v1-alpha1/wallets/users", { method: "POST", headers: { "X-API-KEY": this.crossmintConfig.serverSideApiKey, "Content-Type": "application/json" }, body: JSON.stringify({ email: userEmail, type: "evm-smart-wallet" }) }); if (userResponse.ok) { const userData = await userResponse.json(); userId = userData.id; } } catch (error) { logger2.warn(`Failed to create user, proceeding with anonymous wallet: ${error instanceof Error ? error.message : String(error)}`); } } const walletResponse = await fetch("https://www.crossmint.com/api/v1-alpha1/wallets", { method: "POST", headers: { "X-API-KEY": this.crossmintConfig.serverSideApiKey, "Content-Type": "application/json" }, body: JSON.stringify({ type: "solana-smart-wallet", userId }) }); if (!walletResponse.ok) { const errorData = await walletResponse.json(); throw new Error(`Failed to create wallet: ${JSON.stringify(errorData)}`); } const walletData = await walletResponse.json(); logger2.info("\u2705 Crossmint wallet created successfully"); return { address: walletData.address, walletId: walletData.id, smartWalletAddress: walletData.address, type: "crossmint", email: userEmail }; } catch (error) { logger2.error(`\u274C Failed to create Crossmint wallet: ${error instanceof Error ? error.message : String(error)}`); throw error; } } /** * Get wallet balance */ async getWalletBalance(walletAddress) { try { const publicKey = new PublicKey2(walletAddress); const balance = await this.connection.getBalance(publicKey); return balance; } catch (error) { logger2.error(`Failed to get wallet balance: ${error instanceof Error ? error.message : String(error)}`); return 0; } } /** * Sign a transaction with Crossmint wallet */ async signTransaction(walletId, transaction) { if (!this.isConfigured()) { throw new Error("Crossmint not configured"); } try { logger2.info(`\u{1F510} Signing transaction with wallet ${walletId}...`); const signResponse = await fetch(`https://www.crossmint.com/api/v1-alpha1/wallets/${walletId}/transactions`, { method: "POST", headers: { "X-API-KEY": this.crossmintConfig.serverSideApiKey, "Content-Type": "application/json" }, body: JSON.stringify({ transaction }) }); if (!signResponse.ok) { const errorData = await signResponse.json(); throw new Error(`Failed to sign transaction: ${JSON.stringify(errorData)}`); } const signData = await signResponse.json(); logger2.info("\u2705 Transaction signed successfully"); return signData.signature; } catch (error) { logger2.error(`\u274C Failed to sign transaction: ${error instanceof Error ? error.message : String(error)}`); throw error; } } /** * Get wallet info */ async getWalletInfo(walletId) { if (!this.isConfigured()) { throw new Error("Crossmint not configured"); } try { const response = await fetch(`https://www.crossmint.com/api/v1-alpha1/wallets/${walletId}`, { headers: { "X-API-KEY": this.crossmintConfig.serverSideApiKey } }); if (!response.ok) { throw new Error(`Failed to get wallet info: ${response.statusText}`); } return await response.json(); } catch (error) { logger2.error(`Failed to get wallet info: ${error instanceof Error ? error.message : String(error)}`); throw error; } } /** * List all wallets for the project */ async listWallets() { if (!this.isConfigured()) { throw new Error("Crossmint not configured"); } try { const response = await fetch("https://www.crossmint.com/api/v1-alpha1/wallets", { headers: { "X-API-KEY": this.crossmintConfig.serverSideApiKey } }); if (!response.ok) { throw new Error(`Failed to list wallets: ${response.statusText}`); } return await response.json(); } catch (error) { logger2.error(`Failed to list wallets: ${error instanceof Error ? error.message : String(error)}`); throw error; } } /** * Create a fallback Solana keypair if Crossmint fails */ createFallbackWallet() { logger2.info("\u26A0\uFE0F Creating fallback Solana wallet..."); const keypair = Keypair2.generate(); return { address: keypair.publicKey.toBase58(), privateKey: Buffer.from(keypair.secretKey).toString("base64"), type: "solana" }; } /** * Smart wallet creation with automatic fallback */ async createWalletWithFallback(userEmail) { try { if (this.isConfigured()) { const crossmintWallet = await this.createEmbeddedWallet(userEmail); logger2.info(`\u2705 Created Crossmint smart wallet: ${crossmintWallet.address}`); return crossmintWallet; } else { logger2.info("Crossmint not configured, using fallback wallet"); return this.createFallbackWallet(); } } catch (error) { logger2.warn(`Crossmint wallet creation failed, using fallback: ${error instanceof Error ? error.message : String(error)}`); return this.createFallbackWallet(); } } async stop() { logger2.info("Stopping Crossmint wallet service"); } static async stop(runtime) { const service = runtime.getService(_CrossmintWalletService.serviceType); if (service) { await service.stop(); } } }; // src/services/FalAiService.ts import { Service as Service3, logger as logger3 } from "@elizaos/core"; import { fal } from "@fal-ai/client"; import { z } from "zod"; var FalAiConfigSchema = z.object({ apiKey: z.string().min(1, "FAL API key is required"), fluxModel: z.string().default("fal-ai/flux-pro/kontext"), veoModel: z.string().default("fal-ai/veo3"), veoImageToVideoModel: z.string().default("fal-ai/veo3/fast/image-to-video") }); var ImageGenerationInputSchema = z.object({ prompt: z.string(), image_url: z.string().optional(), guidance_scale: z.number().default(3.5), num_images: z.number().default(1), output_format: z.enum(["jpeg", "png"]).default("jpeg"), safety_tolerance: z.enum(["1", "2", "3", "4", "5", "6"]).default("2"), aspect_ratio: z.enum(["21:9", "16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "9:21"]).optional(), seed: z.number().optional() }); var VideoGenerationInputSchema = z.object({ prompt: z.string(), aspect_ratio: z.enum(["16:9", "9:16", "1:1"]).default("16:9"), duration: z.enum(["8s"]).default("8s"), negative_prompt: z.string().optional(), enhance_prompt: z.boolean().default(true), seed: z.number().optional(), auto_fix: z.boolean().default(true), resolution: z.enum(["720p", "1080p"]).default("720p"), generate_audio: z.boolean().default(true) }); var ImageToVideoInputSchema = z.object({ prompt: z.string(), image_url: z.string(), duration: z.enum(["8s"]).default("8s"), generate_audio: z.boolean().default(true), resolution: z.enum(["720p", "1080p"]).default("720p") }); var FalAiService = class _FalAiService extends Service3 { constructor(runtime) { super(runtime); this.capabilityDescription = "AI-powered image and video generation using FAL AI models including FLUX Pro Kontext and Google Veo3"; this.generationHistory = []; this.config = { apiKey: process.env.FAL_API_KEY || "", fluxModel: process.env.FAL_KONTEXT || "fal-ai/flux-pro/kontext", veoModel: process.env.FAL_VIDEO || "fal-ai/veo3", veoImageToVideoModel: process.env.FAL_IMAGE_TO_VIDEO || "fal-ai/veo3/fast/image-to-video" }; if (this.config.apiKey) { fal.config({ credentials: this.config.apiKey }); } logger3.info("FAL AI Service initialized"); } static { this.serviceType = "fal-ai"; } static async start(runtime) { const service = new _FalAiService(runtime); if (!service.config.apiKey) { logger3.warn("FAL_API_KEY not provided - FAL AI features will be disabled"); } await service.initialize(); return service; } async initialize() { logger3.info("FAL AI Service initialized successfully"); } async stop() { logger3.info("FAL AI Service stopped"); } /** * Generate images using FLUX Pro Kontext */ async generateImage(input) { try { logger3.info(`Generating image with prompt: ${input.prompt.substring(0, 100)}...`); const validatedInput = ImageGenerationInputSchema.parse(input); const result = await fal.subscribe(this.config.fluxModel, { input: validatedInput, logs: true, onQueueUpdate: (update) => { if (update.status === "IN_PROGRESS") { update.logs?.map((log) => log.message).forEach( (msg) => logger3.debug(`FAL Generation: ${msg}`) ); } } }); this.generationHistory.push({ type: "image", timestamp: /* @__PURE__ */ new Date(), model: this.config.fluxModel, success: true, prompt: input.prompt, resultUrl: result.data?.images?.[0]?.url }); logger3.info("Image generated successfully"); return { success: true, images: result.data?.images, requestId: result.requestId, prompt: result.data?.prompt || input.prompt, seed: result.data?.seed }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger3.error(`Image generation failed: ${errorMessage}`); this.generationHistory.push({ type: "image", timestamp: /* @__PURE__ */ new Date(), model: this.config.fluxModel, success: false, prompt: input.prompt }); return { success: false, error: errorMessage }; } } /** * Generate videos using Veo3 */ async generateVideo(input) { try { logger3.info(`Generating video with prompt: ${input.prompt.substring(0, 100)}...`); const validatedInput = VideoGenerationInputSchema.parse(input); const result = await fal.subscribe(this.config.veoModel, { input: validatedInput, logs: true, onQueueUpdate: (update) => { if (update.status === "IN_PROGRESS") { update.logs?.map((log) => log.message).forEach( (msg) => logger3.debug(`FAL Video Generation: ${msg}`) ); } } }); this.generationHistory.push({ type: "video", timestamp: /* @__PURE__ */ new Date(), model: this.config.veoModel, success: true, prompt: input.prompt, resultUrl: result.data?.video?.url }); logger3.info("Video generated successfully"); return { success: true, video: result.data?.video, requestId: result.requestId }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger3.error(`Video generation failed: ${errorMessage}`); this.generationHistory.push({ type: "video", timestamp: /* @__PURE__ */ new Date(), model: this.config.veoModel, success: false, prompt: input.prompt }); return { success: false, error: errorMessage }; } } /** * Generate video from image using Veo3 Image-to-Video */ async generateImageToVideo(input) { try { logger3.info(`Converting image to video: ${input.prompt.substring(0, 100)}...`); const validatedInput = ImageToVideoInputSchema.parse(input); const result = await fal.subscribe(this.config.veoImageToVideoModel, { input: validatedInput, logs: true, onQueueUpdate: (update) => { if (update.status === "IN_PROGRESS") { update.logs?.map((log) => log.message).forEach( (msg) => logger3.debug(`FAL Image-to-Video: ${msg}`) ); } } }); this.generationHistory.push({ type: "image-to-video", timestamp: /* @__PURE__ */ new Date(), model: this.config.veoImageToVideoModel, success: true, prompt: input.prompt, resultUrl: result.data?.video?.url }); logger3.info("Image-to-video conversion completed successfully"); return { success: true, video: result.data?.video, requestId: result.requestId }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger3.error(`Image-to-video conversion failed: ${errorMessage}`); this.generationHistory.push({ type: "image-to-video", timestamp: /* @__PURE__ */ new Date(), model: this.config.veoImageToVideoModel, success: false, prompt: input.prompt }); return { success: false, error: errorMessage }; } } /** * Upload file to FAL storage */ async uploadFile(file) { try { const url = await fal.storage.upload(file); logger3.info(`File uploaded to FAL storage: ${url}`); return url; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger3.error(`File upload failed: ${errorMessage}`); throw new Error(`File upload failed: ${errorMessage}`); } } /** * Get service analytics */ getAnalytics() { const modelCounts = this.generationHistory.reduce((acc, item) => { acc[item.model] = (acc[item.model] || 0) + 1; return acc; }, {}); const mostUsedModel = Object.entries(modelCounts).sort(([, a], [, b]) => b - a)[0]?.[0] || "none"; return { totalImagesGenerated: this.generationHistory.filter((h) => h.type === "image" && h.success).length, totalVideosGenerated: this.generationHistory.filter((h) => h.type === "video" && h.success).length, totalImageToVideoConversions: this.generationHistory.filter((h) => h.type === "image-to-video" && h.success).length, totalApiCalls: this.generationHistory.length, lastGenerationTime: this.generationHistory.length > 0 ? this.generationHistory[this.generationHistory.length - 1].timestamp : null, mostUsedModel }; } /** * Get service state */ getState() { return { isConfigured: !!this.config.apiKey, apiKeyPresent: !!this.config.apiKey, lastError: null, // Could track last error here analytics: this.getAnalytics() }; } /** * Get recent generation history */ getRecentGenerations(limit = 10) { return this.generationHistory.slice(-limit).reverse(); } }; // src/actions/claimFees.ts import { logger as logger4 } from "@elizaos/core"; var claimFeesAction = { name: "CLAIM_BAGS_FEES", similes: ["CLAIM_FEES", "COLLECT_EARNINGS", "CLAIM_REWARDS", "GET_FEES"], description: "Claims available fees from Bags protocol tokens", validate: async (runtime, message, state) => { const text = message.content.text?.toLowerCase(); if (!text) return false; const triggers = [ "claim fees", "claim all fees", "collect fees", "collect earnings", "claim rewards", "get my fees", "withdraw fees", "claim from bags" ]; return triggers.some((trigger) => text.includes(trigger)); }, handler: async (runtime, message, state, options, callback) => { try { const bagsService = runtime.getService("bags"); if (!bagsService) { await callback?.({ text: "Bags service is not available. Please check the configuration.", error: true }); return { success: false, text: "Bags service not found", error: new Error("Bags service not available") }; } const text = message.content.text?.toLowerCase() || ""; let results; if (text.includes("all") || !text.match(/[A-Za-z0-9]{32,}/)) { await callback?.({ text: "\u{1F504} Checking for claimable fees across all tokens...", action: "CLAIM_BAGS_FEES" }); results = await bagsService.claimAllFees(); } else { const tokenMintMatch = text.match(/([A-Za-z0-9]{32,})/); if (tokenMintMatch) { const tokenMint = tokenMintMatch[1]; await callback?.({ text: `\u{1F504} Claiming fees for token: ${tokenMint.slice(0, 8)}...${tokenMint.slice(-8)}`, action: "CLAIM_BAGS_FEES" }); const result = await bagsService.claimFeesForToken(tokenMint); results = result ? [result] : []; } else { results = await bagsService.claimAllFees(); } } if (results.length === 0) { await callback?.({ text: "\u{1F4AD} No claimable fees found at this time. Your tokens might not have generated fees yet, or fees may have already been claimed.", action: "CLAIM_BAGS_FEES" }); return { success: true, text: "No fees to claim", values: { claimedCount: 0, totalClaimed: 0 }, data: { actionName: "CLAIM_BAGS_FEES", results: [] } }; } const totalClaimed = results.reduce((sum, result) => sum + result.claimedAmount, 0); const successfulClaims = results.filter((r) => r.success).length; let responseText = `\u2705 Fee claiming completed! `; responseText += `\u{1F4B0} Total claimed: ${totalClaimed.toFixed(6)} SOL `; responseText += `\u{1F4CA} Successful claims: ${successfulClaims}/${results.length} `; if (results.length <= 5) { responseText += `Details: `; for (const result of results) { const tokenShort = `${result.tokenMint.slice(0, 8)}...${result.tokenMint.slice(-8)}`; responseText += `\u2022 ${tokenShort}: ${result.claimedAmount.toFixed(6)} SOL `; } } else { responseText += `View full details in the analytics dashboard.`; } await callback?.({ text: responseText, action: "CLAIM_BAGS_FEES" }); return { success: true, text: `Successfully claimed ${totalClaimed.toFixed(6)} SOL from ${successfulClaims} tokens`, values: { claimedCount: successfulClaims, totalClaimed, results }, data: { actionName: "CLAIM_BAGS_FEES", totalClaimed, successfulClaims, results } }; } catch (error) { logger4.error(`Error in claim fees action: ${error instanceof Error ? error.message : String(error)}`); await callback?.({ text: "\u274C An error occurred while claiming fees. Please try again later.", error: true }); return { success: false, text: "Failed to claim fees", error: error instanceof Error ? error : new Error(String(error)), data: { actionName: "CLAIM_BAGS_FEES", errorMessage: error instanceof Error ? error.message : String(error) } }; } }, examples: [ [ { name: "{{userName}}", content: { text: "claim all my fees from bags", actions: [] } }, { name: "{{agentName}}", content: { text: "\u{1F504} Checking for claimable fees across all tokens...\n\n\u2705 Fee claiming completed!\n\n\u{1F4B0} Total claimed: 0.125000 SOL\n\u{1F4CA} Successful claims: 3/3\n\nDetails:\n\u2022 4k3Dyjlb...8xMp1a2K: 0.045000 SOL\n\u2022 7wB5nCx9...2nFp4k7L: 0.032000 SOL\n\u2022 9mK8pLq2...5vGh3n8M: 0.048000 SOL", actions: ["CLAIM_BAGS_FEES"] } } ], [ { name: "{{userName}}", content: { text: "collect my earnings", actions: [] } }, { name: "{{agentName}}", content: { text: "\u{1F504} Checking for claimable fees across all tokens...\n\n\u{1F4AD} No claimable fees found at this time. Your tokens might not have generated fees yet, or fees may have already been claimed.", actions: ["CLAIM_BAGS_FEES"] } } ], [ { name: "{{userName}}", content: { text: "claim fees for token 4k3Dyjlb8xMp1a2KcNvFp9L7wB5nCx92nFp4k7L5vGh3n8M", actions: [] } }, { name: "{{agentName}}", content: { text: "\u{1F504} Claiming fees for token: 4k3Dyjlb...5vGh3n8M\n\n\u2705 Fee claiming completed!\n\n\u{1F4B0} Total claimed: 0.045000 SOL\n\u{1F4CA} Successful claims: 1/1\n\nDetails:\n\u2022 4k3Dyjlb...5vGh3n8M: 0.045000 SOL", actions: ["CLAIM_BAGS_FEES"] } } ] ] }; // src/actions/launchToken.ts import { logger as logger6 } from "@elizaos/core"; // src/utils/tokenParser.ts import { logger as logger5 } from "@elizaos/core"; function parseTokenParameters(text) { try { const params = {}; const nameMatch = text.match(/(?:name|token name):\s*([^\n,]+)/i); if (nameMatch) { params.name = nameMatch[1].trim(); } const symbolMatch = text.match(/(?:symbol|ticker):\s*\$?([A-Z0-9]{1,10})/i) || text.match(/\$([A-Z0-9]{1,10})/); if (symbolMatch) { params.symbol = symbolMatch[1].trim().toUpperCase(); } const descMatch = text.match(/(?:description|desc|about):\s*([^\n,]+)/i); if (descMatch) { params.description = descMatch[1].trim(); } const imageMatch = text.match(/(?:image|img|logo):\s*(https?:\/\/[^\s,]+)/i); if (imageMatch) { params.imageUrl = imageMatch[1].trim(); } const websiteMatch = text.match(/(?:website|site|web):\s*(https?:\/\/[^\s,]+)/i); if (websiteMatch) { params.website = websiteMatch[1].trim(); } const twitterMatch = text.match(/(?:twitter|x\.com):\s*(https?:\/\/[^\s,]+)/i); if (twitterMatch) { params.twitter = twitterMatch[1].trim(); } const buyMatch = text.match(/(?:initial buy|buy amount):\s*(\d*\.?\d+)/i); if (buyMatch) { params.initialBuyAmountSOL = parseFloat(buyMatch[1]); } if (!params.name || !params.symbol || !params.description) { return null; } return params; } catch (error) { logger5.error(`Error parsing token parameters: ${error instanceof Error ? error.message : String(error)}`); return null; } } // src/actions/launchToken.ts var launchTokenAction = { name: "LAUNCH_BAGS_TOKEN", similes: ["LAUNCH_TOKEN", "CREATE_TOKEN", "DEPLOY_TOKEN", "MINT_TOKEN"], description: "Launches a new token on the Bags protocol with specified parameters", validate: async (runtime, message, state) => { const text = message.content.text?.toLowerCase(); if (!text) return false; const triggers = [ "launch token", "create token", "deploy token", "mint token", "new token", "launch on bags", "create on bags" ]; return triggers.some((trigger) => text.includes(trigger)); }, handler: async (runtime, message, state, options, callback) => { try { const bagsService = runtime.getService("bags"); if (!bagsService) { await callback?.({ text: "Bags service is not available. Please check the configuration.", error: true }); return { success: false, text: "Bags service not found", error: new Error("Bags service not available") }; } const text = message.content.text || ""; const tokenParams = parseTokenParameters(text); if (!tokenParams) { await callback?.({ text: "\u274C Unable to parse token parameters. Please provide the token details in this format:\n\n**Launch token:**\n\u2022 Name: Your Token Name\n\u2022 Symbol: TOKEN\n\u2022 Description: A brief description of your token\n\u2022 Image: https://example.com/image.png (optional)\n\u2022 Website: https://yourwebsite.com (optional)\n\u2022 Twitter: https://twitter.com/yourtoken (optional)\n\u2022 Initial buy: 0.01 SOL (optional, defaults to 0.01)", error: true }); return { success: false, text: "Invalid token parameters", error: new Error("Could not parse token parameters from message") }; } if (!tokenParams.name || !tokenParams.symbol || !tokenParams.description) { await callback?.({ text: "\u274C Missing required fields. Please provide at least:\n\u2022 **Name**: Token name\n\u2022 **Symbol**: Token symbol (2-10 characters)\n\u2022 **Description**: Token description", error: true }); return { success: false, text: "Missing required token parameters", error: new Error("Name, symbol, and description are required") }; } await callback?.({ text: `\u{1F504} Launching token with the following parameters: \u{1F4DB} **Name**: ${tokenParams.name} \u{1F3F7}\uFE0F **Symbol**: $${tokenP