UNPKG

@agentdao/core

Version:

Core functionality, skills, and ready-made UI components for AgentDAO - Web3 subscriptions, content generation, social media, help support, live chat, RSS fetching, web search, and agent pricing integration

790 lines (789 loc) 31.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EnhancedWeb3SubscriptionSkill = void 0; const ethers_1 = require("ethers"); const Web3SubscriptionSkill_1 = require("./Web3SubscriptionSkill"); class EnhancedWeb3SubscriptionSkill extends Web3SubscriptionSkill_1.Web3SubscriptionSkill { /** * Get default configuration for basic mode */ static getDefaultConfig(agentId) { return { agentId, agentName: `${agentId} Agent`, domain: 'agentdao.com', mode: 'basic', adaoToken: { address: process.env.ADAO_CONTRACT_ADDRESS || "0x1ef7Be0aBff7d1490e952eC1C7476443A66d6b72", // Real ADAO address decimals: 18, network: 'base' }, plans: { premium: { name: "Premium", description: "Premium subscription plan with full access", features: [ "Unlimited API calls", "Priority support", "Advanced analytics", "Custom integrations", "White-label options" ], pricing: { monthly: { price: 100, discount: 0 }, quarterly: { price: 270, discount: 10 }, annually: { price: 1000, discount: 17 } }, billing: { defaultPeriod: 'monthly', allowPeriodChange: true, prorationEnabled: true } }, basic: { name: "Basic", description: "Basic subscription plan", features: [ "1000 API calls per month", "Email support", "Basic analytics", "Standard integrations" ], pricing: { monthly: { price: 50, discount: 0 }, quarterly: { price: 135, discount: 10 }, annually: { price: 500, discount: 17 } }, billing: { defaultPeriod: 'monthly', allowPeriodChange: true, prorationEnabled: false } } }, provider: { rpcUrl: process.env.BASE_RPC_URL || "https://mainnet.base.org", chainId: 8453, explorer: 'https://basescan.org' }, payment: { autoApprove: true, requireConfirmation: false, refundPolicy: { enabled: true, gracePeriod: 7 }, billing: { allowTrial: true, trialDays: 7, gracePeriodDays: 3 } }, integration: { webhookUrl: process.env.WEBHOOK_SUBSCRIPTION_CREATED, redirectUrl: process.env.REDIRECT_URL, successMessage: 'Subscription created successfully!', errorMessage: 'Failed to create subscription. Please try again.' }, analytics: { trackRevenue: true, trackUsage: true, exportData: false } }; } constructor(config) { // Handle basic mode configuration let finalConfig; if (config.mode === 'basic') { console.log("🚀 [EnhancedWeb3SubscriptionSkill] Initializing in BASIC mode with minimal configuration"); // Merge with default configuration const defaultConfig = EnhancedWeb3SubscriptionSkill.getDefaultConfig(config.agentId); finalConfig = { ...defaultConfig, ...config, // Preserve user-provided values over defaults agentId: config.agentId, agentName: config.agentName || defaultConfig.agentName, plans: config.plans || defaultConfig.plans }; } else { // Advanced mode - use configuration as provided console.log("🚀 [EnhancedWeb3SubscriptionSkill] Initializing in ADVANCED mode with full configuration"); finalConfig = config; } // Call super with the final configuration super(finalConfig); this.webhookQueue = []; this.analyticsCache = new Map(); this.enhancedConfig = finalConfig; // Log initialization for debugging console.log("🚀 [EnhancedWeb3SubscriptionSkill] Initializing with config:", { agentId: this.enhancedConfig.agentId, agentName: this.enhancedConfig.agentName, mode: this.enhancedConfig.mode || 'advanced', hasPlans: !!this.enhancedConfig.plans && Object.keys(this.enhancedConfig.plans).length > 0, planCount: this.enhancedConfig.plans ? Object.keys(this.enhancedConfig.plans).length : 0, hasProvider: !!this.enhancedConfig.provider, hasADaoToken: !!this.enhancedConfig.adaoToken }); } /** * Check if the subscription skill is ready for use */ isReady() { // Use public methods to check readiness const hasSigner = this.hasSigner(); const hasPlans = this.hasPlans(); const hasProvider = this.hasProvider(); const hasADaoToken = this.hasADaoToken(); const ready = hasSigner && hasPlans && hasProvider && hasADaoToken; if (!ready) { console.warn("⚠️ [EnhancedWeb3SubscriptionSkill] Not ready:", { hasSigner, hasPlans, hasProvider, hasADaoToken }); } else { console.log("✅ [EnhancedWeb3SubscriptionSkill] Ready for use"); } return ready; } /** * Check if signer is set */ hasSigner() { try { // Try to access signer through a public method or property return !!this.signer; } catch { return false; } } /** * Check if plans are configured */ hasPlans() { try { // Use the enhanced config which is public return !!this.enhancedConfig.plans && Object.keys(this.enhancedConfig.plans).length > 0; } catch { return false; } } /** * Check if provider is configured */ hasProvider() { try { return !!this.enhancedConfig.provider; } catch { return false; } } /** * Check if ADAO token is configured */ hasADaoToken() { try { return !!this.enhancedConfig.adaoToken; } catch { return false; } } /** * Get readiness status with details */ getReadinessStatus() { return { ready: this.isReady(), hasSigner: this.hasSigner(), hasPlans: this.hasPlans(), hasProvider: this.hasProvider(), hasADaoToken: this.hasADaoToken(), planCount: this.enhancedConfig.plans ? Object.keys(this.enhancedConfig.plans).length : 0, availablePlans: this.enhancedConfig.plans ? Object.keys(this.enhancedConfig.plans) : [] }; } /** * Validate plan exists before processing */ validatePlan(planId) { if (!this.enhancedConfig.plans) { console.error("❌ [EnhancedWeb3SubscriptionSkill] No plans configured. Please add plans to your configuration or use default plans."); return false; } if (!this.enhancedConfig.plans[planId]) { const availablePlans = Object.keys(this.enhancedConfig.plans); console.error(`❌ [EnhancedWeb3SubscriptionSkill] Plan '${planId}' not found. Available plans: ${availablePlans.join(', ')}. Please use one of the available plan IDs.`); return false; } return true; } /** * Validate billing period */ validateBillingPeriod(billingPeriod) { const validPeriods = ['monthly', 'quarterly', 'annually']; if (!validPeriods.includes(billingPeriod)) { console.error(`❌ [EnhancedWeb3SubscriptionSkill] Invalid billing period '${billingPeriod}'. Valid periods: ${validPeriods.join(', ')}. Please use one of the supported billing periods.`); return false; } return true; } /** * Validate user address format */ validateUserAddress(userAddress) { if (!userAddress) { console.error(`❌ [EnhancedWeb3SubscriptionSkill] User address is required. Please provide a valid Ethereum address.`); return false; } if (!userAddress.startsWith('0x')) { console.error(`❌ [EnhancedWeb3SubscriptionSkill] Invalid Ethereum address: ${userAddress}. Address must start with '0x'. Example: 0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6`); return false; } if (userAddress.length !== 42) { console.error(`❌ [EnhancedWeb3SubscriptionSkill] Invalid Ethereum address length: ${userAddress}. Address must be exactly 42 characters (including '0x'). Example: 0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6`); return false; } // Additional validation using ethers.js try { if (!ethers_1.ethers.isAddress(userAddress)) { console.error(`❌ [EnhancedWeb3SubscriptionSkill] Invalid Ethereum address format: ${userAddress}. Please provide a valid 42-character address starting with 0x. Example: 0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6`); return false; } } catch (error) { console.error(`❌ [EnhancedWeb3SubscriptionSkill] Address validation failed: ${userAddress}. Please check the address format. Example: 0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6`); return false; } return true; } /** * Enhanced createSubscription with comprehensive validation */ async createSubscription(userAddress, planId, billingPeriod) { // Pre-flight checks if (!this.isReady()) { throw new Error("Subscription skill not ready. Check readiness status."); } if (!this.validateUserAddress(userAddress)) { throw new Error(`Invalid user address format: ${userAddress}`); } if (!this.validatePlan(planId)) { throw new Error(`Plan '${planId}' not found. Available plans: ${Object.keys(this.enhancedConfig.plans).join(', ')}`); } if (!this.validateBillingPeriod(billingPeriod)) { throw new Error(`Invalid billing period '${billingPeriod}'. Valid periods: monthly, quarterly, annually`); } // Check if plan supports the billing period const plan = this.enhancedConfig.plans[planId]; if (!plan.pricing[billingPeriod]) { throw new Error(`Plan '${planId}' does not support '${billingPeriod}' billing period`); } console.log(`[EnhancedWeb3SubscriptionSkill] Creating subscription:`, { userAddress, planId, billingPeriod, planName: plan.name }); try { const subscription = await super.createSubscription(userAddress, planId, billingPeriod); console.log(`✅ [EnhancedWeb3SubscriptionSkill] Subscription created successfully:`, { subscriptionId: subscription.id, userAddress, planId, billingPeriod }); return subscription; } catch (error) { console.error(`❌ [EnhancedWeb3SubscriptionSkill] Failed to create subscription:`, error); throw error; } } /** * Enhanced checkSubscription with validation */ async checkSubscription(userAddress) { if (!this.validateUserAddress(userAddress)) { throw new Error(`Invalid user address format: ${userAddress}`); } try { return await super.checkSubscription(userAddress); } catch (error) { console.error(`❌ [EnhancedWeb3SubscriptionSkill] Failed to check subscription:`, error); throw error; } } /** * Get health status with detailed feedback */ getHealthStatus() { const readinessStatus = this.getReadinessStatus(); const missingConfig = []; if (!readinessStatus.hasSigner) { missingConfig.push('PLATFORM_PRIVATE_KEY environment variable'); } if (!readinessStatus.hasProvider) { missingConfig.push('BASE_RPC_URL environment variable'); } if (!readinessStatus.hasADaoToken) { missingConfig.push('ADAO_CONTRACT_ADDRESS environment variable'); } if (!readinessStatus.hasPlans) { missingConfig.push('Subscription plans configuration'); } return { timestamp: new Date().toISOString(), skill: { ready: this.isReady(), hasSigner: this.hasSigner(), hasPlans: this.hasPlans(), hasProvider: this.hasProvider(), hasADaoToken: this.hasADaoToken() }, config: { agentId: this.enhancedConfig.agentId, agentName: this.enhancedConfig.agentName, mode: this.enhancedConfig.mode || 'advanced', planCount: this.enhancedConfig.plans ? Object.keys(this.enhancedConfig.plans).length : 0, availablePlans: this.enhancedConfig.plans ? Object.keys(this.enhancedConfig.plans) : [] }, status: this.isReady() ? 'healthy' : 'unhealthy', missingConfig, recommendations: this.isReady() ? [] : [ 'Set required environment variables', 'Configure subscription plans', 'Set up provider connection', 'Configure ADAO token contract' ] }; } // ===================== // BACKWARD COMPATIBLE METHODS (unchanged) // ===================== // All existing methods work exactly the same // createSubscription, checkSubscription, cancelSubscription, etc. // are inherited from Web3SubscriptionSkill // ===================== // NEW ENHANCED METHODS // ===================== /** * Async initialization method for better error handling */ async initialize() { try { console.log("🔄 [EnhancedWeb3SubscriptionSkill] Starting async initialization..."); // Check if already initialized if (this.isReady()) { console.log("✅ [EnhancedWeb3SubscriptionSkill] Already initialized and ready"); return true; } // Set up signer if available if (process.env.PLATFORM_PRIVATE_KEY) { try { const providerUrl = this.enhancedConfig.provider?.rpcUrl || "https://mainnet.base.org"; const provider = new ethers_1.ethers.JsonRpcProvider(providerUrl); const signer = new ethers_1.ethers.Wallet(process.env.PLATFORM_PRIVATE_KEY, provider); this.setSigner(signer); console.log("✅ [EnhancedWeb3SubscriptionSkill] Signer configured successfully"); } catch (error) { console.warn("⚠️ [EnhancedWeb3SubscriptionSkill] Failed to configure signer:", error); } } // Wait a bit for everything to settle await new Promise(resolve => setTimeout(resolve, 100)); const isReady = this.isReady(); if (isReady) { console.log("✅ [EnhancedWeb3SubscriptionSkill] Initialization completed successfully"); } else { console.warn("⚠️ [EnhancedWeb3SubscriptionSkill] Initialization completed but skill is not ready"); const healthStatus = this.getHealthStatus(); console.warn("Missing configuration:", healthStatus.missingConfig); } return isReady; } catch (error) { console.error("❌ [EnhancedWeb3SubscriptionSkill] Initialization failed:", error); throw new Error(`Failed to initialize subscription skill: ${error instanceof Error ? error.message : 'Unknown error'}. Please check your configuration and try again.`); } } /** * Get available payment methods for a user across all chains */ async getPaymentMethods(userAddress) { const methods = []; for (const [tokenAddress, tokenConfig] of Object.entries(this.enhancedConfig.tokens || {})) { try { const provider = new ethers_1.ethers.JsonRpcProvider(this.enhancedConfig.chains?.[tokenConfig.chainId]?.rpcUrl); if (tokenAddress === 'native') { // Native token (ETH, MATIC, etc.) const balance = await provider.getBalance(userAddress); const balanceFormatted = parseFloat(ethers_1.ethers.formatEther(balance)); methods.push({ type: 'native', address: 'native', symbol: tokenConfig.symbol, decimals: 18, chainId: tokenConfig.chainId, balance: balanceFormatted }); } else { // ERC-20 token const tokenContract = new ethers_1.ethers.Contract(tokenAddress, ['function balanceOf(address) view returns (uint256)'], provider); const balance = await tokenContract.balanceOf(userAddress); const balanceFormatted = parseFloat(ethers_1.ethers.formatUnits(balance, tokenConfig.decimals)); methods.push({ type: 'token', address: tokenAddress, symbol: tokenConfig.symbol, decimals: tokenConfig.decimals, chainId: tokenConfig.chainId, balance: balanceFormatted }); } } catch (error) { console.warn(`Failed to get balance for token ${tokenAddress}:`, error); } } return methods; } /** * Create subscription with enhanced features */ async createEnhancedSubscription(userAddress, planId, billingPeriod, paymentMethod) { // Create base subscription const baseSubscription = await this.createSubscription(userAddress, planId, billingPeriod); // Enhance with additional data const enhancedSubscription = { ...baseSubscription, paymentMethod, chainId: paymentMethod.chainId, analytics: { totalPayments: 1, totalRevenue: baseSubscription.price, averageSessionDuration: 0, featureUsage: {}, churnRate: 0, lifetimeValue: baseSubscription.price } }; // Send webhook await this.sendWebhook('subscriptionCreated', { subscriptionId: enhancedSubscription.id, userAddress, planId, billingPeriod, paymentMethod, amount: baseSubscription.price }); // Track analytics await this.trackAnalytics('subscription_created', { userAddress, planId, amount: baseSubscription.price, paymentMethod: paymentMethod.symbol }); return enhancedSubscription; } /** * Upgrade subscription to a higher plan */ async upgradeSubscription(userAddress, newPlanId, newPeriod) { const currentStatus = await this.checkSubscription(userAddress); if (!currentStatus.hasActiveSubscription) { throw new Error('No active subscription found'); } const currentPlan = this.enhancedConfig.plans[currentStatus.planId]; const newPlan = this.enhancedConfig.plans[newPlanId]; if (!newPlan) { throw new Error(`Plan ${newPlanId} not found`); } // Calculate prorated amount const proratedAmount = await this.calculateProratedUpgrade(userAddress, currentStatus.planId, newPlanId, newPeriod || currentStatus.billingPeriod); // Create upgrade record const upgradeRecord = { id: `upgrade-${Date.now()}`, fromPlan: currentStatus.planId, toPlan: newPlanId, fromPeriod: currentStatus.billingPeriod, toPeriod: newPeriod || currentStatus.billingPeriod, proratedAmount, timestamp: new Date() }; // Process the upgrade (this would interact with smart contracts) console.log(`Upgrading subscription for user: ${userAddress}`); console.log(`From: ${currentStatus.planId} to: ${newPlanId}`); console.log(`Prorated amount: ${proratedAmount}`); // Send webhook await this.sendWebhook('subscriptionUpgraded', { userAddress, fromPlan: currentStatus.planId, toPlan: newPlanId, proratedAmount }); // Return enhanced subscription return { id: `upgraded-${Date.now()}`, userAddress, planId: newPlanId, planName: newPlan.name, billingPeriod: newPeriod || currentStatus.billingPeriod, price: proratedAmount, monthlyPrice: this.calculateEnhancedMonthlyPrice(proratedAmount, newPeriod || currentStatus.billingPeriod), startDate: new Date(), endDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days nextBillingDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), status: 'active', features: newPlan.features, discountApplied: 0, paymentMethod: await this.getDefaultPaymentMethod(userAddress), chainId: 8453, // Default to Base upgradeHistory: [upgradeRecord] }; } /** * Downgrade subscription to a lower plan */ async downgradeSubscription(userAddress, newPlanId, newPeriod) { const currentStatus = await this.checkSubscription(userAddress); if (!currentStatus.hasActiveSubscription) { throw new Error('No active subscription found'); } const newPlan = this.enhancedConfig.plans[newPlanId]; if (!newPlan) { throw new Error(`Plan ${newPlanId} not found`); } // Calculate credit for downgrade const creditAmount = await this.calculateDowngradeCredit(userAddress, currentStatus.planId, newPlanId, newPeriod || currentStatus.billingPeriod); // Process the downgrade console.log(`Downgrading subscription for user: ${userAddress}`); console.log(`To: ${newPlanId}, Credit: ${creditAmount}`); // Send webhook await this.sendWebhook('subscriptionDowngraded', { userAddress, toPlan: newPlanId, creditAmount }); return { id: `downgraded-${Date.now()}`, userAddress, planId: newPlanId, planName: newPlan.name, billingPeriod: newPeriod || currentStatus.billingPeriod, price: 0, // No charge for downgrade monthlyPrice: 0, startDate: new Date(), endDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), nextBillingDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), status: 'active', features: newPlan.features, discountApplied: 0, paymentMethod: await this.getDefaultPaymentMethod(userAddress), chainId: 8453 }; } /** * Process refund for a subscription */ async processRefund(userAddress, amount, reason) { if (!this.enhancedConfig.refunds?.enabled) { throw new Error('Refunds are not enabled for this subscription'); } const currentStatus = await this.checkSubscription(userAddress); if (!currentStatus.hasActiveSubscription) { throw new Error('No active subscription found'); } const refundRecord = { id: `refund-${Date.now()}`, amount, reason, status: this.enhancedConfig.refunds.requireApproval ? 'pending' : 'approved', timestamp: new Date() }; // If auto-refund is enabled, process immediately if (this.enhancedConfig.refunds.autoRefund && !this.enhancedConfig.refunds.requireApproval) { refundRecord.status = 'processed'; // Here you would interact with smart contracts to process the refund console.log(`Processing refund of ${amount} for user: ${userAddress}`); } // Send webhook await this.sendWebhook('refundIssued', { userAddress, amount, reason, status: refundRecord.status }); return refundRecord; } /** * Get enhanced analytics for a subscription */ async getEnhancedAnalytics(userAddress) { const cached = this.analyticsCache.get(userAddress); if (cached) { return cached; } const subscription = await this.checkSubscription(userAddress); if (!subscription.hasActiveSubscription) { throw new Error('No active subscription found'); } // Mock analytics data (in real implementation, this would come from database) const analytics = { totalPayments: 1, totalRevenue: 100, // Mock data averageSessionDuration: 45, // minutes featureUsage: { chat: 150, analytics: 25, support: 10 }, churnRate: 0.05, // 5% lifetimeValue: 100 }; this.analyticsCache.set(userAddress, analytics); return analytics; } /** * Get enhanced revenue statistics */ async getEnhancedRevenueStats() { const baseStats = await this.getRevenueStats(); // Mock enhanced data (in real implementation, this would come from database) const enhancedStats = { ...baseStats, byChain: { 8453: baseStats.totalRevenue * 0.8, // 80% on Base 137: baseStats.totalRevenue * 0.2 // 20% on Polygon }, byToken: { [this.enhancedConfig.adaoToken.address]: baseStats.totalRevenue * 0.7, // 70% ADAO '0x0000000000000000000000000000000000000000': baseStats.totalRevenue * 0.3 // 30% native }, byPeriod: { monthly: baseStats.totalRevenue * 0.4, quarterly: baseStats.totalRevenue * 0.35, annually: baseStats.totalRevenue * 0.25 }, churnRate: 0.05, averageLifetimeValue: 250, refundRate: 0.02, upgradeRate: 0.15 }; return enhancedStats; } // ===================== // PRIVATE HELPER METHODS // ===================== async sendWebhook(event, data) { const webhookUrl = this.enhancedConfig.webhooks?.[event]; if (!webhookUrl) return; const payload = { event, subscriptionId: data.subscriptionId || 'unknown', userAddress: data.userAddress, data, timestamp: new Date() }; try { const response = await fetch(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'User-Agent': 'AgentDAO-Web3Subscription/1.0' }, body: JSON.stringify(payload) }); if (!response.ok) { console.warn(`Webhook failed for event ${event}:`, response.statusText); // Queue for retry this.webhookQueue.push(payload); } } catch (error) { console.error(`Webhook error for event ${event}:`, error); this.webhookQueue.push(payload); } } async trackAnalytics(event, data) { if (!this.enhancedConfig.enhancedAnalytics?.trackUsage) return; try { // Send to analytics endpoint await fetch('/api/analytics/track', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ event, agentId: this.enhancedConfig.agentId, data, timestamp: new Date().toISOString() }) }); } catch (error) { console.error('Analytics tracking failed:', error); } } async getDefaultPaymentMethod(userAddress) { const methods = await this.getPaymentMethods(userAddress); return methods[0] || { type: 'token', address: this.enhancedConfig.adaoToken.address, symbol: 'ADAO', decimals: this.enhancedConfig.adaoToken.decimals, chainId: 8453, balance: 0 }; } async calculateProratedUpgrade(userAddress, fromPlanId, toPlanId, newPeriod) { const fromPlan = this.enhancedConfig.plans[fromPlanId]; const toPlan = this.enhancedConfig.plans[toPlanId]; if (!fromPlan || !toPlan) { throw new Error('Invalid plan configuration'); } const fromPrice = fromPlan.pricing[newPeriod].price; const toPrice = toPlan.pricing[newPeriod].price; // Simple proration: difference between plans return Math.max(0, toPrice - fromPrice); } async calculateDowngradeCredit(userAddress, fromPlanId, toPlanId, newPeriod) { const fromPlan = this.enhancedConfig.plans[fromPlanId]; const toPlan = this.enhancedConfig.plans[toPlanId]; if (!fromPlan || !toPlan) { throw new Error('Invalid plan configuration'); } const fromPrice = fromPlan.pricing[newPeriod].price; const toPrice = toPlan.pricing[newPeriod].price; // Credit for downgrade return Math.max(0, fromPrice - toPrice); } calculateEnhancedMonthlyPrice(price, billingPeriod) { switch (billingPeriod) { case 'monthly': return price; case 'quarterly': return price / 3; case 'annually': return price / 12; default: return price; } } } exports.EnhancedWeb3SubscriptionSkill = EnhancedWeb3SubscriptionSkill;