import { ethers } from 'ethers'; import { GoTakeSDK, NetworkId, getNetworkByName } from '../src'; import * as dotenv from 'dotenv'; import path from 'path'; dotenv.config(); // Import videos config from hiltonchen project const configPath = path.resolve(__dirname, '../../steps_sites/hiltonchen/src/data/videos-config.ts'); interface VideoConfig { id: string; contentId: number; price: number; // USDC price duration: string; // "MM:SS" format title: string; category: string; } interface ContentDeployConfig { contentId: number; priceETH: string; viewCount: number; isActive: boolean; usdcPrice: number; // Original USDC price from config needsUSDCPrice: boolean; // Whether to set USDC price } class HiltonContentDeployer { private sdk: GoTakeSDK; private dryRun: boolean; private paymentTokenInfo!: { address: string; decimals: number; symbol?: string }; private paymentTokenInfoReady: Promise; constructor(network: NetworkId | string, signer: ethers.Signer, dryRun: boolean = false) { this.sdk = new GoTakeSDK({ network: network, signer: signer }); this.dryRun = dryRun; this.paymentTokenInfoReady = this.resolvePaymentTokenInfo(network); } get provider() { return this.sdk.provider; } // Load and transform videos config private async loadVideosConfig(): Promise { try { const { videosConfig } = await import(configPath); console.log(`Found ${videosConfig.videos.length} videos in config`); const contentConfigs: ContentDeployConfig[] = videosConfig.videos.map((video: VideoConfig) => { return { contentId: video.contentId, priceETH: "1", // Uniform 1 ETH price viewCount: 0, // Single purchase mode isActive: true, usdcPrice: video.price, // Store original USDC price needsUSDCPrice: video.price > 0 // Only set USDC price if > 0 }; }); // Sort by contentId for consistent ordering contentConfigs.sort((a, b) => a.contentId - b.contentId); console.log('Content configuration prepared:'); contentConfigs.forEach(config => { const usdcInfo = config.needsUSDCPrice ? `, ${config.usdcPrice} USDC` : ', FREE'; console.log(` Content ID ${config.contentId}: ${config.priceETH} ETH${usdcInfo}, ViewCount: ${config.viewCount}`); }); return contentConfigs; } catch (error) { console.error('Failed to load videos config:', error.message); throw error; } } private async resolvePaymentTokenInfo(network: NetworkId | string): Promise { const { tokenConfig } = await import(configPath); const paymentTokenMap = (tokenConfig as any).paymentToken || {}; let chainId: number; if (typeof network === 'number') { chainId = network; } else if (typeof network === 'string') { const netCfg = getNetworkByName(network); if (netCfg) { chainId = netCfg.id; } else if (!isNaN(Number(network))) { chainId = Number(network); } else { throw new Error(`Unable to resolve chainId for network identifier "${network}"`); } } else { chainId = Number(network as unknown as number); } const tokenInfo = paymentTokenMap[chainId]; if (!tokenInfo) { throw new Error(`Payment token config not found for chainId ${chainId}`); } this.paymentTokenInfo = { address: tokenInfo.address, decimals: tokenInfo.decimals, symbol: tokenInfo.symbol }; console.log(`Resolved payment token ${this.paymentTokenInfo.symbol ?? ''} at ${this.paymentTokenInfo.address} with decimals ${this.paymentTokenInfo.decimals}`); } async checkAdminRights(): Promise { console.log('\nChecking Admin Rights'); console.log('====================='); try { await this.sdk.videoPayment.init(); const wrapper = (this.sdk.videoPayment as any)._videoPaymentWrapper; if (wrapper) { const contractAddress = wrapper.getContractAddress('videoPayment'); console.log(`Using VideoPayment contract: ${contractAddress}`); } console.log('Admin rights verified - you can manage content'); } catch (error) { console.error('Admin rights check failed:', error.message); throw error; } } async setUSDCPrices(contentConfigs: ContentDeployConfig[]): Promise { const usdcConfigs = contentConfigs.filter(config => config.needsUSDCPrice); if (usdcConfigs.length === 0) { console.log('\nNo USDC prices to set (all content is free)'); return; } // Ensure payment token info is ready await this.paymentTokenInfoReady; console.log(`\nSetting USDC Prices for ${usdcConfigs.length} Content Items`); console.log('='.repeat(60)); if (this.dryRun) { console.log('\nDRY RUN MODE - USDC prices that would be set:'); usdcConfigs.forEach(config => { console.log(` Content ID ${config.contentId}: ${config.usdcPrice} USDC (stored as ${this.paymentTokenInfo.decimals}-decimal format)`); }); return; } for (const config of usdcConfigs) { try { console.log(`\nSetting Content ID ${config.contentId} to ${config.usdcPrice} USDC...`); // Get optimal gas configuration const gasPrices = await this.sdk.getGasPrice({ multiplier: 1.1, priorityMultiplier: 1.05 }); console.log(` Max Fee: ${ethers.utils.formatUnits(gasPrices.maxFeePerGas, "gwei")} gwei`); const priceWei = ethers.utils.parseUnits(config.usdcPrice.toString(), this.paymentTokenInfo.decimals); console.log(` Setting price: ${priceWei.toString()} wei (${this.paymentTokenInfo.decimals}-decimal format)`); const txHash = await this.sdk.videoPayment.setContentPrice( config.contentId, priceWei, this.paymentTokenInfo.address, { gasConfig: { maxFeePerGas: gasPrices.maxFeePerGas, maxPriorityFeePerGas: gasPrices.maxPriorityFeePerGas } } ); console.log(` Transaction sent: ${txHash}`); // Wait for confirmation console.log(' Waiting for confirmation...'); const receipt = await this.provider.waitForTransaction(txHash); if (receipt.status === 1) { console.log(` ✅ Successfully set ${config.usdcPrice} USDC for Content ID ${config.contentId}`); } else { console.log(` ❌ Transaction failed for Content ID ${config.contentId}`); } // Wait for 2 block confirmations before next transaction if (config !== usdcConfigs[usdcConfigs.length - 1]) { console.log(' Waiting for 2 block confirmations before next transaction...'); let currentBlock = await this.provider.getBlockNumber(); const targetBlock = receipt.blockNumber + 2; while (currentBlock < targetBlock) { await new Promise(resolve => setTimeout(resolve, 2000)); // Check every 2 seconds currentBlock = await this.provider.getBlockNumber(); console.log(` Current block: ${currentBlock}, target: ${targetBlock}`); } console.log(` ✅ 2 blocks confirmed. Proceeding...`); } } catch (error) { console.error(` ❌ Failed to set USDC price for Content ID ${config.contentId}:`, error.message); // Continue with next update instead of stopping continue; } } console.log(`\n✅ USDC price setting completed! Updated ${usdcConfigs.length} content items.`); } async deployAllContent(): Promise { console.log('\nDeploying Hiltonchen Content to Base Mainnet'); console.log('==========================================='); try { // Load content configurations const contentConfigs = await this.loadVideosConfig(); if (contentConfigs.length === 0) { console.log('No content configurations found'); return; } console.log(`\nPreparing to deploy ${contentConfigs.length} content items`); if (this.dryRun) { console.log('\nDRY RUN MODE - No actual transactions will be sent'); console.log('Content items that would be created:'); contentConfigs.forEach(config => { const usdcInfo = config.needsUSDCPrice ? `, ${config.usdcPrice} USDC` : ', FREE'; console.log(` ID: ${config.contentId}, Price: ${config.priceETH} ETH${usdcInfo}, ViewCount: ${config.viewCount}`); }); // Show USDC price setting plan await this.setUSDCPrices(contentConfigs); return; } // Execute batch content creation console.log('\nExecuting batch content creation...'); // Get optimal gas configuration for batch creation const gasPrices = await this.sdk.getGasPrice({ multiplier: 1.1, // 降低倍数避免过高gas费用 priorityMultiplier: 1.05 }); console.log(`Batch creation gas - Max Fee: ${ethers.utils.formatUnits(gasPrices.maxFeePerGas, "gwei")} gwei`); const txHash = await this.sdk.videoPayment.batchSetContentConfig({ contentIds: contentConfigs.map(c => c.contentId), prices: contentConfigs.map(c => c.priceETH), token: ethers.constants.AddressZero, viewCounts: contentConfigs.map(c => c.viewCount), isActiveArray: contentConfigs.map(c => c.isActive) }, { gasConfig: { maxFeePerGas: gasPrices.maxFeePerGas, maxPriorityFeePerGas: gasPrices.maxPriorityFeePerGas } }); console.log(`Batch content creation transaction sent!`); console.log(`Transaction Hash: ${txHash}`); // Wait for transaction confirmation console.log('\nWaiting for transaction confirmation...'); const receipt = await this.provider.waitForTransaction(txHash); if (receipt.status === 1) { console.log(`Successfully deployed ${contentConfigs.length} content items!`); console.log(`Transaction confirmed in block ${receipt.blockNumber}`); console.log(`Gas used: ${receipt.gasUsed?.toString()}`); console.log('\nDeployed Content IDs:'); contentConfigs.forEach(config => { console.log(` ${config.contentId}: ${config.priceETH} ETH`); }); // Step 2: Set USDC prices for paid content await this.setUSDCPrices(contentConfigs); } else { console.log(`Transaction failed in block ${receipt.blockNumber}`); throw new Error('Transaction failed - content was not created'); } } catch (error) { console.error('Failed to deploy content:', error.message); throw error; } } } async function main() { console.log('Hiltonchen Content Deployment Tool'); console.log('==================================\n'); // Environment configuration const NETWORK = process.env.NETWORK || 'Base Mainnet'; 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 const 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 deployer = new HiltonContentDeployer(networkConfig.id, signer, isDryRun); // Check admin rights first await deployer.checkAdminRights(); // Deploy all content await deployer.deployAllContent(); console.log('\nDeployment completed successfully!'); } catch (error) { console.error('Deployment failed:', error); console.error('Details:', error.message); process.exit(1); } } // Run the script if (require.main === module) { main().catch(console.error); }