@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
426 lines (425 loc) • 17 kB
JavaScript
;
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 {
constructor(config) {
super(config);
this.webhookQueue = [];
this.analyticsCache = new Map();
// Validate safe address configuration
const safeAddress = config.platformFee?.safeAddress || process.env.PLATFORM_SAFE_ADDRESS || '';
if (!safeAddress) {
console.warn('⚠️ PLATFORM_SAFE_ADDRESS not configured. Platform fees will not be properly tracked.');
}
else if (!safeAddress.startsWith('0x') || safeAddress.length !== 42) {
console.warn('⚠️ PLATFORM_SAFE_ADDRESS appears to be invalid. Expected 42-character hex address starting with 0x.');
}
this.enhancedConfig = {
...config,
platformFee: {
percentage: 10, // Reduced from 15% to 10%
safeAddress,
autoTransfer: true,
...config.platformFee
},
chains: {
8453: {
name: 'Base',
rpcUrl: 'https://mainnet.base.org',
explorer: 'https://basescan.org',
nativeToken: 'ETH',
...config.chains?.[8453]
},
...config.chains
},
tokens: {
// Default ADAO token on Base
[config.adaoToken.address]: {
name: 'ADAO Token',
symbol: 'ADAO',
decimals: config.adaoToken.decimals,
chainId: 8453,
...config.tokens?.[config.adaoToken.address]
},
...config.tokens
}
};
}
// =====================
// BACKWARD COMPATIBLE METHODS (unchanged)
// =====================
// All existing methods work exactly the same
// createSubscription, checkSubscription, cancelSubscription, etc.
// are inherited from Web3SubscriptionSkill
// =====================
// NEW ENHANCED METHODS
// =====================
/**
* 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;