import { ethers } from 'ethers'; import { GoTakeSDK, NetworkId, getNetworkById, getNetworkByName, TokenUtils } from '../src'; import * as dotenv from 'dotenv'; dotenv.config(); // Removed unused interfaces to fix linting warnings interface ContentManagerOptions { batchSize?: number; confirmActions?: boolean; dryRun?: boolean; } // Removed unused ContentBatchConfig interface interface ContentSummary { contentId: number; status: string; error?: string; } class ContentManager { private sdk: GoTakeSDK; private options: ContentManagerOptions; constructor( network: NetworkId | string, signer: ethers.Signer, options: ContentManagerOptions = {} ) { this.sdk = new GoTakeSDK({ network: network, signer: signer }); this.options = { batchSize: 10, confirmActions: true, dryRun: false, ...options }; } get provider() { return this.sdk.provider; } async checkAdminRights(): Promise { console.log('\nπŸ” Checking Admin Rights'); console.log('========================'); try { await this.sdk.videoPayment.init(); // Debug: Print the actual contract address being used const wrapper = (this.sdk.videoPayment as any)._videoPaymentWrapper; if (wrapper) { const contractAddress = wrapper.getContractAddress('videoPayment'); console.log(`\nπŸ“ Using VideoPayment contract: ${contractAddress}`); } console.log('\nβœ… Admin rights verified - you can manage content'); } catch (error) { console.error('\n❌ Admin rights check failed:', error.message); throw error; } } async createContent( contentId: number, priceETH: string, viewCount: number, isActive: boolean = true ): Promise { console.log(`\nπŸ“ Creating Content ID: ${contentId}`); console.log('='.repeat(40)); try { const priceWei = ethers.utils.parseEther(priceETH); console.log('\nContent Configuration:'); console.log(` Content ID: ${contentId}`); console.log(` Price: ${priceETH} ETH (${priceWei.toString()} wei)`); console.log(` View Count: ${viewCount} views`); console.log(` Active: ${isActive ? 'Yes' : 'No'}`); if (this.options.dryRun) { console.log('\nπŸ§ͺ DRY RUN MODE - No actual transaction will be sent'); return; } // Get optimal gas configuration console.log('\nβ›½ Getting optimal gas configuration...'); const gasPrices = await this.sdk.getGasPrice({ multiplier: 1.2, // 20% buffer for base fee priorityMultiplier: 1.1 // 10% buffer for priority fee }); console.log(` Base Fee: ${gasPrices.baseFeePerGas ? ethers.utils.formatUnits(gasPrices.baseFeePerGas, "gwei") + ' gwei' : 'N/A'}`); console.log(` Max Fee: ${ethers.utils.formatUnits(gasPrices.maxFeePerGas, "gwei")} gwei`); console.log(` Priority Fee: ${gasPrices.maxPriorityFeePerGas ? ethers.utils.formatUnits(gasPrices.maxPriorityFeePerGas, "gwei") + ' gwei' : 'N/A'}`); console.log('\nπŸ’Ύ Creating content...'); const txHash = await this.sdk.videoPayment.setContentConfig({ contentId, price: priceETH, token: ethers.constants.AddressZero, viewCount: viewCount, isActive }, { gasConfig: { maxFeePerGas: gasPrices.maxFeePerGas, maxPriorityFeePerGas: gasPrices.maxPriorityFeePerGas // Let system auto-estimate gas limit } }); console.log(`πŸ“€ Content creation transaction sent!`); console.log(` Transaction Hash: ${txHash}`); console.log(` Content ID: ${contentId}`); // Wait for transaction confirmation console.log('\n⏳ Waiting for transaction confirmation...'); const receipt = await this.provider.waitForTransaction(txHash); if (receipt.status === 1) { console.log(`βœ… Content created successfully!`); console.log(`βœ… Transaction confirmed in block ${receipt.blockNumber}`); console.log(`βœ… Gas used: ${receipt.gasUsed?.toString()}`); } else { console.log(`❌ Transaction failed in block ${receipt.blockNumber}`); console.log(`❌ Gas used: ${receipt.gasUsed?.toString()}`); throw new Error('Transaction failed - content was not created'); } } catch (error) { console.error(`❌ Failed to create content ${contentId}:`, error.message); throw error; } } async batchCreateContent(configurations: Array<{ contentId: number; priceETH: string; viewCount: number; isActive?: boolean; }>): Promise { console.log(`\nπŸ“¦ Batch Creating ${configurations.length} Content Items`); console.log('='.repeat(60)); if (configurations.length === 0) { console.log('❌ No configurations provided'); return; } // Prepare batch data const contentIds = configurations.map(c => c.contentId); const nativePrices = configurations.map(c => c.priceETH); const viewCounts = configurations.map(c => c.viewCount); const isActiveArray = configurations.map(c => c.isActive ?? true); console.log('Batch Configuration Summary:'); configurations.forEach((config, index) => { console.log(` ${index + 1}. Content ${config.contentId}: ${config.priceETH} ETH, ${config.viewCount} views, ${config.isActive ? 'Active' : 'Inactive'}`); }); if (this.options.dryRun) { console.log('\nπŸ§ͺ DRY RUN MODE - No actual transaction will be sent'); return; } try { console.log('\nπŸ’Ύ Executing batch content creation...'); const txHash = await this.sdk.videoPayment.batchSetContentConfig({ contentIds, prices: nativePrices, token: ethers.constants.AddressZero, viewCounts, isActiveArray }); console.log(`πŸ“€ Batch content creation transaction sent!`); console.log(` Transaction Hash: ${txHash}`); // Wait for transaction confirmation console.log('\n⏳ Waiting for transaction confirmation...'); const receipt = await this.provider.waitForTransaction(txHash); if (receipt.status === 1) { console.log(`βœ… Batch content creation successful!`); console.log(`βœ… Transaction confirmed in block ${receipt.blockNumber}`); console.log(`βœ… Created ${configurations.length} content items`); console.log(`βœ… Gas used: ${receipt.gasUsed?.toString()}`); } else { console.log(`❌ Transaction failed in block ${receipt.blockNumber}`); console.log(`❌ Gas used: ${receipt.gasUsed?.toString()}`); throw new Error('Batch transaction failed - content items were not created'); } } catch (error) { console.error('❌ Batch content creation failed:', error.message); throw error; } } async updateContentPrice(contentId: number, newPriceETH: string): Promise { console.log(`\nπŸ’° Updating Price for Content ID: ${contentId}`); console.log('='.repeat(50)); try { console.log(`New Price: ${newPriceETH} ETH`); if (this.options.dryRun) { console.log('\nπŸ§ͺ DRY RUN MODE - No actual transaction will be sent'); return; } // Get optimal gas configuration console.log('\nβ›½ Getting optimal gas configuration...'); const gasPrices = await this.sdk.getGasPrice({ multiplier: 1.2, // 20% buffer for base fee priorityMultiplier: 1.1 // 10% buffer for priority fee }); console.log(` Base Fee: ${gasPrices.baseFeePerGas ? ethers.utils.formatUnits(gasPrices.baseFeePerGas, "gwei") + ' gwei' : 'N/A'}`); console.log(` Max Fee: ${ethers.utils.formatUnits(gasPrices.maxFeePerGas, "gwei")} gwei`); console.log(` Priority Fee: ${gasPrices.maxPriorityFeePerGas ? ethers.utils.formatUnits(gasPrices.maxPriorityFeePerGas, "gwei") + ' gwei' : 'N/A'}`); console.log('\nπŸ’Ύ Updating price...'); // Use the new unified price update method with address(0) for native token const txHash = await this.sdk.videoPayment.setContentPrice( contentId, ethers.utils.parseEther(newPriceETH), undefined, { gasConfig: { maxFeePerGas: gasPrices.maxFeePerGas, maxPriorityFeePerGas: gasPrices.maxPriorityFeePerGas // Let system auto-estimate gas limit } } ); console.log(`βœ… Price updated successfully!`); console.log(` Transaction Hash: ${txHash}`); console.log(` Content ID: ${contentId}`); console.log(` New Price: ${newPriceETH} ETH`); } catch (error) { console.error(`❌ Failed to update price for content ${contentId}:`, error.message); throw error; } } async updateERC20Price(contentId: number, tokenAddress: string, tokenPrice: string, decimals: number = 18): Promise { console.log(`\nπŸͺ™ Updating ERC20 Price for Content ID: ${contentId}`); console.log('='.repeat(50)); try { console.log(`Token Address: ${tokenAddress}`); console.log(`New Price: ${tokenPrice} tokens`); console.log(`Decimals: ${decimals}`); if (this.options.dryRun) { console.log('\nπŸ§ͺ DRY RUN MODE - No actual transaction will be sent'); return; } // Get optimal gas configuration console.log('\nβ›½ Getting optimal gas configuration...'); const gasPrices = await this.sdk.getGasPrice({ multiplier: 1.2, // 20% buffer for base fee priorityMultiplier: 1.1 // 10% buffer for priority fee }); console.log(` Base Fee: ${gasPrices.baseFeePerGas ? ethers.utils.formatUnits(gasPrices.baseFeePerGas, "gwei") + ' gwei' : 'N/A'}`); console.log(` Max Fee: ${ethers.utils.formatUnits(gasPrices.maxFeePerGas, "gwei")} gwei`); console.log(` Priority Fee: ${gasPrices.maxPriorityFeePerGas ? ethers.utils.formatUnits(gasPrices.maxPriorityFeePerGas, "gwei") + ' gwei' : 'N/A'}`); console.log('\nπŸ’Ύ Updating ERC20 price...'); // Use the updated updateContentPrice method with token address const priceWei = ethers.utils.parseUnits(tokenPrice, decimals); const txHash = await this.sdk.videoPayment.setContentPrice( contentId, priceWei, tokenAddress, { gasConfig: { maxFeePerGas: gasPrices.maxFeePerGas, maxPriorityFeePerGas: gasPrices.maxPriorityFeePerGas // Let system auto-estimate gas limit } } ); console.log(`βœ… ERC20 price updated successfully!`); console.log(` Transaction Hash: ${txHash}`); console.log(` Content ID: ${contentId}`); console.log(` Token: ${tokenAddress}`); console.log(` New Price: ${tokenPrice} tokens`); } catch (error) { console.error(`❌ Failed to update ERC20 price for content ${contentId}:`, error.message); throw error; } } /** * Convert NetworkId to network key string * @param networkId Network ID * @returns Network key */ private getNetworkKey(networkId: number): string { const networkMap: Record = { 8453: 'base', 84532: 'base_sepolia', }; const key = networkMap[networkId]; if (!key) { throw new Error(`Network ID ${networkId} not supported. Only Base Mainnet (8453) and Base Sepolia (84532) are supported.`); } return key; } async getMockERC20Address(): Promise { try { await this.sdk.videoPayment.init(); // Check if we're on Base Mainnet const networkId = this.sdk.networkId; if (networkId === 8453) { // Base Mainnet console.log(' Using USDC on Base Mainnet'); return '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base Mainnet } // Always use gotake-contracts to get mockERC20 address const wrapper = (this.sdk.videoPayment as any)._videoPaymentWrapper; if (wrapper) { return wrapper.getContractAddress('mockERC20'); } } catch (error) { console.warn('Could not get MockERC20 address from config, using gotake-contracts fallback'); } // Fallback: Use gotake-contracts directly const { getContractAddress } = require('gotake-contracts'); try { return getContractAddress('base_sepolia', 'mockERC20'); } catch (error) { console.error('Failed to get mockERC20 from gotake-contracts:', error.message); throw new Error('Could not determine mockERC20 address'); } } async waitForTransactionAndDelay( transactionHash: string, additionalDelayMs: number = 3000 ): Promise { try { console.log('⏳ Waiting for transaction confirmation...'); const receipt = await this.provider.waitForTransaction(transactionHash, 1, 30000); console.log(`βœ… Transaction confirmed in block ${receipt.blockNumber}`); if (additionalDelayMs > 0) { console.log(`⏳ Waiting additional ${additionalDelayMs}ms for state sync...`); await new Promise(resolve => setTimeout(resolve, additionalDelayMs)); console.log('βœ… State sync delay completed'); } } catch (error) { console.warn(`⚠️ Transaction wait failed: ${error.message}`); throw error; } } async retryGetContentInfo( contentId: number, expectedTokenAddress: string, maxRetries: number = 5, baseDelayMs: number = 1000 ): Promise { const backoffFactor = 1.5; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { console.log(`πŸ”„ Attempt ${attempt}/${maxRetries}: Getting content info...`); const contentInfo = await this.sdk.videoPayment.getContentInfo(contentId); // Check if expected ERC20 token price exists if (contentInfo.tokenPrices[expectedTokenAddress]) { console.log(`βœ… ERC20 token price found on attempt ${attempt}`); return contentInfo; } if (attempt < maxRetries) { const delayMs = Math.floor(baseDelayMs * Math.pow(backoffFactor, attempt - 1)); console.log(`⏳ ERC20 price not found, retrying in ${delayMs}ms...`); await new Promise(resolve => setTimeout(resolve, delayMs)); } else { console.log(`⚠️ ERC20 price not found after ${maxRetries} attempts`); return contentInfo; // Return anyway for further analysis } } catch (error) { if (attempt === maxRetries) { console.error(`❌ Final attempt failed: ${error.message}`); throw error; } const delayMs = Math.floor(baseDelayMs * Math.pow(backoffFactor, attempt - 1)); console.log(`⚠️ Attempt ${attempt} failed, retrying in ${delayMs}ms...`); await new Promise(resolve => setTimeout(resolve, delayMs)); } } throw new Error('Retry logic error - should not reach here'); } async testERC20Features(contentId: number): Promise { console.log(`\nπŸ§ͺ Testing ERC20 Features for Content ID: ${contentId}`); console.log('='.repeat(60)); try { // Step 1: Create content if it doesn't exist console.log('\nπŸ“ Step 1: Creating test content...'); let contentCreated = false; try { await this.createContent(contentId, '0.00001', 5, true); contentCreated = true; } catch (error) { console.log(' Content may already exist, continuing...'); } // Step 2: Get MockERC20 address console.log('\nπŸ” Step 2: Getting MockERC20 address...'); const mockERC20Address = await this.getMockERC20Address(); console.log(` MockERC20 Address: ${mockERC20Address}`); // Step 3: Set ERC20 price with enhanced feedback console.log('\nπŸ’° Step 3: Setting ERC20 price...'); console.log(` Setting price for token: ${mockERC20Address}`); console.log(` Price: 0.01 tokens`); if (this.options.dryRun) { console.log('\nπŸ§ͺ DRY RUN MODE - No actual transaction will be sent'); return; } console.log('\nπŸ’Ύ Updating ERC20 price...'); const priceWei = ethers.utils.parseUnits('0.01', 18); const txHash = await this.sdk.videoPayment.setContentPrice( contentId, priceWei, mockERC20Address ); console.log(`βœ… ERC20 price transaction sent!`); console.log(` Transaction Hash: ${txHash}`); // Step 4: Wait for transaction confirmation and state sync console.log('\n⏳ Step 4: Waiting for transaction confirmation and state sync...'); await this.waitForTransactionAndDelay(txHash, 3000); // Step 5: Retry get content info with ERC20 verification console.log('\nπŸ”„ Step 5: Verifying ERC20 price with retry logic...'); const contentInfo = await this.retryGetContentInfo(contentId, mockERC20Address, 5, 1000); console.log('\nπŸ“Š Final Content Information:'); console.log(` Content ID: ${contentInfo.contentId}`); console.log(` Native Price: ${ethers.utils.formatEther(contentInfo.nativePrice)} ETH`); console.log(` View Count: ${contentInfo.viewCount}`); console.log(` Active: ${contentInfo.isActive}`); console.log('\nπŸ’° All Token Prices:'); Object.entries(contentInfo.tokenPrices).forEach(([token, price]) => { if (token === ethers.constants.AddressZero) { console.log(` πŸ’Ž ETH (${token}): ${ethers.utils.formatEther(price as ethers.BigNumber)} ETH`); } else { console.log(` πŸͺ™ ERC20 (${token}): ${ethers.utils.formatUnits(price as ethers.BigNumber, 18)} tokens`); } }); // Step 6: Final verification and result console.log('\n🎯 Step 6: Final Test Result Verification...'); const erc20Price = contentInfo.tokenPrices[mockERC20Address]; if (erc20Price) { const priceFormatted = ethers.utils.formatUnits(erc20Price, 18); console.log(`\nπŸŽ‰ ERC20 Test PASSED!`); console.log(` βœ… MockERC20 price successfully set: ${priceFormatted} tokens`); console.log(` βœ… Token address: ${mockERC20Address}`); console.log(` βœ… Transaction confirmed: ${txHash}`); if (contentCreated) { console.log(` βœ… New content created: ID ${contentId}`); } } else { console.log(`\n⚠️ ERC20 Test INCONCLUSIVE!`); console.log(` ❌ MockERC20 price not found for token ${mockERC20Address}`); console.log(` βœ… Transaction was successful: ${txHash}`); console.log(` πŸ“Š Total tokens detected: ${Object.keys(contentInfo.tokenPrices).length}`); console.log('\nπŸ’‘ Possible reasons:'); console.log(' β€’ State sync delay longer than expected'); console.log(' β€’ Contract address mismatch'); console.log(' β€’ Network congestion'); console.log('\nπŸ”§ Troubleshooting suggestions:'); console.log(' β€’ Wait a few more seconds and try: npm run purchase-content check 999'); console.log(' β€’ Verify contract addresses are correct'); console.log(' β€’ Check network connectivity'); } } catch (error) { console.error(`\nπŸ’₯ ERC20 features test failed:`, error.message); console.error('\nπŸ” Error analysis:'); if (error.message.includes('timeout')) { console.error(' β€’ Transaction timeout - network may be congested'); console.error(' β€’ Try again with higher gas settings'); } else if (error.message.includes('revert')) { console.error(' β€’ Transaction reverted - check permissions and parameters'); } else if (error.message.includes('network')) { console.error(' β€’ Network connectivity issue'); console.error(' β€’ Check your internet connection and RPC endpoint'); } else { console.error(' β€’ Unexpected error occurred'); } throw error; } } async bulkUpdatePrices(updates: Array<{ contentId: number; newPriceETH: string }>): Promise { console.log(`\nπŸ’° Bulk Price Update (${updates.length} items)`); console.log('='.repeat(50)); console.log('Price Update Summary:'); updates.forEach((update, index) => { console.log(` ${index + 1}. Content ${update.contentId}: ${update.newPriceETH} ETH`); }); if (this.options.dryRun) { console.log('\nπŸ§ͺ DRY RUN MODE - No actual transactions will be sent'); return; } let successful = 0; let failed = 0; for (const update of updates) { try { console.log(`\nπŸ’Ύ Updating Content ${update.contentId}...`); await this.updateContentPrice(update.contentId, update.newPriceETH); successful++; console.log(`βœ… Success`); } catch (error) { console.error(`❌ Failed: ${error.message}`); failed++; } } console.log(`\nπŸ“Š Bulk Update Results:`); console.log(` Successful: ${successful}`); console.log(` Failed: ${failed}`); console.log(` Success Rate: ${((successful / updates.length) * 100).toFixed(1)}%`); } async checkSingleContent(contentId: number): Promise { console.log(`\nπŸ” Checking Content ID: ${contentId}`); console.log('='.repeat(50)); try { console.log(`\nπŸ“Š Getting detailed content information...`); const contentInfo = await this.sdk.videoPayment.getContentInfo(contentId); console.log('\nπŸ“‹ Content Details:'); console.log(` Content ID: ${contentInfo.contentId}`); console.log(` Active: ${contentInfo.isActive ? 'Yes' : 'No'}`); console.log(` View Count: ${contentInfo.viewCount}`); console.log('\nπŸ’° Pricing Information:'); console.log(` Native Price: ${ethers.utils.formatEther(contentInfo.nativePrice)} ETH`); console.log('\nπŸͺ™ Supported Payment Tokens:'); const tokenCount = Object.keys(contentInfo.tokenPrices).length; console.log(` Total Supported Tokens: ${tokenCount}`); if (tokenCount > 0) { // Get network key for token lookup const networkKey = this.getNetworkKey(this.sdk.networkId); Object.entries(contentInfo.tokenPrices).forEach(([tokenAddress, price], index) => { if (tokenAddress === ethers.constants.AddressZero) { console.log(` ${index + 1}. πŸ’Ž ETH (${tokenAddress}): ${ethers.utils.formatEther(price as ethers.BigNumber)} ETH`); } else { // Get token information from SDK const tokenInfo = TokenUtils.getTokenInfo(networkKey, tokenAddress); const tokenName = tokenInfo?.name || 'Unknown Token'; const tokenSymbol = tokenInfo?.symbol || 'UNKNOWN'; const tokenDecimals = tokenInfo?.decimals || 18; // Format price with correct decimals const formattedPrice = TokenUtils.formatTokenAmount(price as ethers.BigNumber, tokenAddress, networkKey, 6); console.log(` ${index + 1}. πŸͺ™ ${tokenName} (${tokenSymbol})`); console.log(` Address: ${tokenAddress}`); console.log(` Price: ${formattedPrice} ${tokenSymbol}`); console.log(` Decimals: ${tokenDecimals}`); } }); } else { console.log(' ❌ No payment tokens configured'); } console.log('\nβœ… Content check completed successfully'); } catch (error) { console.error(`❌ Failed to check content ${contentId}:`, error.message); throw error; } } async listContentStatus(contentIds: number[]): Promise { console.log(`\nπŸ“‹ Content Status Check (${contentIds.length} items)`); console.log('='.repeat(60)); const summaries: ContentSummary[] = []; for (const contentId of contentIds) { try { console.log(`Checking Content ${contentId}...`); // Use new content info method to get detailed status const contentInfo = await this.sdk.videoPayment.getContentInfo(contentId); let status = 'Available'; if (!contentInfo.isActive) { status = 'Inactive'; } console.log(` Price: ${ethers.utils.formatEther(contentInfo.nativePrice)} ETH`); console.log(` Views: ${contentInfo.viewCount}`); console.log(` Active: ${contentInfo.isActive ? 'Yes' : 'No'}`); console.log(` Tokens: ${Object.keys(contentInfo.tokenPrices).length}`); summaries.push({ contentId, status }); } catch (error) { console.error(`❌ Failed to check content ${contentId}:`, error.message); summaries.push({ contentId, status: 'Error', error: error.message }); } } // Display content table this.displayContentTable(summaries); return summaries; } private displayContentTable(summaries: ContentSummary[]): void { console.log('\nπŸ“Š Content Status Table:'); console.log('β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”'); console.log('β”‚ Content ID β”‚ Status β”‚'); console.log('β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€'); summaries.forEach(summary => { const contentId = summary.contentId.toString().padStart(10); const status = summary.status.padStart(12); console.log(`β”‚ ${contentId} β”‚ ${status} β”‚`); }); console.log('β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜'); // Summary statistics const availableCount = summaries.filter(s => s.status === 'Available').length; const inactiveCount = summaries.filter(s => s.status === 'Inactive').length; const errorCount = summaries.filter(s => s.status === 'Error').length; console.log(`\nπŸ“ˆ Summary Statistics:`); console.log(` Total Content: ${summaries.length}`); console.log(` Available: ${availableCount} (${((availableCount / summaries.length) * 100).toFixed(1)}%)`); console.log(` Inactive: ${inactiveCount} (${((inactiveCount / summaries.length) * 100).toFixed(1)}%)`); console.log(` Errors: ${errorCount} (${((errorCount / summaries.length) * 100).toFixed(1)}%)`); } async generateContentReport(contentIds: number[]): Promise { console.log('\nπŸ“Š Generating Content Report'); console.log('='.repeat(50)); const summaries = await this.listContentStatus(contentIds); console.log('\nπŸ“ˆ Report Analysis:'); const availableContent = summaries.filter(s => s.status === 'Available'); const inactiveContent = summaries.filter(s => s.status === 'Inactive'); const errorContent = summaries.filter(s => s.status === 'Error'); if (availableContent.length > 0) { console.log(`\nβœ… Available Content: ${availableContent.length} items`); availableContent.forEach(s => { console.log(` β€’ Content ${s.contentId}: Active and ready for purchase`); }); } if (inactiveContent.length > 0) { console.log(`\n⏸️ Inactive Content: ${inactiveContent.length} items`); inactiveContent.forEach(s => { console.log(` β€’ Content ${s.contentId}: Configured but not active`); }); } if (errorContent.length > 0) { console.log(`\n❌ Content with Errors: ${errorContent.length} items`); errorContent.forEach(s => { console.log(` β€’ Content ${s.contentId}: ${s.error}`); }); } console.log('\nπŸ’‘ Recommendations:'); if (errorContent.length > 0) { console.log(` β€’ Review and fix ${errorContent.length} content items with errors`); } if (inactiveContent.length > 0) { console.log(` β€’ Consider activating ${inactiveContent.length} inactive content items`); } if (availableContent.length > 0) { console.log(` β€’ ${availableContent.length} content items are ready for purchase`); } console.log('\nβœ… Report generation complete'); } } async function main() { console.log('πŸŽ›οΈ GoTake Content Manager'); console.log('==========================\n'); // Environment configuration - support network selection const NETWORK = process.env.NETWORK || 'Base Sepolia'; const PRIVATE_KEY = process.env.PRIVATE_KEY; if (!PRIVATE_KEY) { console.error('❌ Please set PRIVATE_KEY environment variable'); process.exit(1); } try { // Resolve network configuration let networkConfig; if (typeof NETWORK === 'string' && !isNaN(Number(NETWORK))) { // Network ID provided const networkId = Number(NETWORK) as NetworkId; networkConfig = getNetworkById(networkId); } else { // Network name provided networkConfig = getNetworkByName(NETWORK); } if (!networkConfig) { console.error(`❌ Unsupported network: ${NETWORK}`); console.log('Supported networks: Base Mainnet, Base Sepolia, Base Goerli, Ethereum Mainnet, Ethereum Sepolia'); process.exit(1); } console.log(`🌐 Network: ${networkConfig.name} (ID: ${networkConfig.id})`); console.log(`πŸ”— RPC: ${networkConfig.rpcUrl}`); // Create provider and signer const provider = new ethers.providers.JsonRpcProvider(networkConfig.rpcUrl); const signer = new ethers.Wallet(PRIVATE_KEY, provider); // Parse command line options const isDryRun = process.argv.includes('--dry-run'); const skipConfirm = process.argv.includes('--no-confirm'); const manager = new ContentManager(networkConfig.id, signer, { dryRun: isDryRun, confirmActions: !skipConfirm }); // Check admin rights first await manager.checkAdminRights(); // Parse command const command = process.argv[2]; switch (command) { case 'create': // Create single content const contentId = parseInt(process.argv[3]); const price = process.argv[4]; const views = parseInt(process.argv[5]); const active = process.argv[7] !== 'false'; if (!contentId || !price || !views) { console.error('Usage: npm run manage-content create [active]'); process.exit(1); } await manager.createContent(contentId, price, views, active); break; case 'batch-create': // Batch create content from configuration const batchConfig = [ { contentId: 10, priceETH: '0.001', viewCount: 5 }, { contentId: 11, priceETH: '0.002', viewCount: 10 }, { contentId: 12, priceETH: '0.005', viewCount: 20 } ]; await manager.batchCreateContent(batchConfig); break; case 'update-price': // Update single content price const updateContentId = parseInt(process.argv[3]); const newPrice = process.argv[4]; if (!updateContentId || !newPrice) { console.error('Usage: npm run manage-content update-price '); process.exit(1); } await manager.updateContentPrice(updateContentId, newPrice); break; case 'update-erc20-price': // Update ERC20 token price const erc20ContentId = parseInt(process.argv[3]); const tokenAddress = process.argv[4]; const tokenPrice = process.argv[5]; const decimals = parseInt(process.argv[6]) || 18; if (!erc20ContentId || !tokenAddress || !tokenPrice) { console.error('Usage: npm run manage-content update-erc20-price [decimals]'); process.exit(1); } await manager.updateERC20Price(erc20ContentId, tokenAddress, tokenPrice, decimals); break; case 'test-erc20': // Test ERC20 features comprehensively const testContentId = parseInt(process.argv[3]) || 999; await manager.testERC20Features(testContentId); break; case 'list': // List all content const defaultContentIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; await manager.listContentStatus(defaultContentIds); break; case 'check': // Check single content const checkContentId = parseInt(process.argv[3]); if (!checkContentId) { console.error('Usage: npm run manage-content check '); process.exit(1); } await manager.checkSingleContent(checkContentId); break; case 'list-range': // List content in range const startId = parseInt(process.argv[3]); const endId = parseInt(process.argv[4]); if (!startId || !endId) { console.error('Usage: npm run manage-content list-range '); process.exit(1); } const rangeIds = Array.from({ length: endId - startId + 1 }, (_, i) => startId + i); await manager.listContentStatus(rangeIds); break; case 'bulk-price-update': // Bulk update prices const priceUpdates = [ { contentId: 1, newPriceETH: '0.001' }, { contentId: 2, newPriceETH: '0.002' }, { contentId: 3, newPriceETH: '0.003' } ]; await manager.bulkUpdatePrices(priceUpdates); break; case 'report': // Generate comprehensive report const reportContentIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; await manager.generateContentReport(reportContentIds); break; default: console.log('Available commands:'); console.log(' check - Check single content details'); console.log(' create [active] - Create single content'); console.log(' batch-create - Create multiple content (predefined)'); console.log(' update-price - Update ETH content price'); console.log(' update-erc20-price [decimals] - Update ERC20 token price'); console.log(' test-erc20 [contentId] - Test ERC20 features comprehensively'); console.log(' list - List all content (default IDs)'); console.log(' list-range - List content in ID range'); console.log(' bulk-price-update - Bulk update prices (predefined)'); console.log(' report - Generate comprehensive report'); console.log('\nOptions:'); console.log(' --dry-run - Simulate operations without executing'); console.log(' --no-confirm - Skip confirmation prompts'); console.log('\nExamples:'); console.log(' npm run manage-content check 9'); console.log(' npm run manage-content create 1 0.01 10 true'); console.log(' npm run manage-content update-price 1 0.02'); console.log(' npm run manage-content update-erc20-price 1 0x4D1F4F683AB7122fBb77aB544aC03c5678acA968 100'); console.log(' npm run manage-content test-erc20 999'); console.log(' npm run manage-content list-range 1 20'); console.log(' npm run manage-content report --dry-run'); break; } } catch (error) { console.error('❌ Content management operation failed:', error); console.error('Details:', error.message); process.exit(1); } } // Run the script if (require.main === module) { main().catch(console.error); }