import { ProverError, formatProveAmount, createProverOnChain, stakeToProverOnChain, SecureCredentialManager } from '../../utils.js';
import { CalibrationResult } from '../../types.js';
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { exec } from 'child_process';
import { promisify } from 'util';
import * as os from 'os';
const execAsync = promisify(exec);
// Succinct Network constants from official docs
const SUCCINCT_STAKING_ADDRESS = '0x8F1B4633630b90C273E834C14924298ab6D1DA02';
const PROVE_TOKEN_ADDRESS = '0xC47B85472b40435c0D96db5Df3e9B4C368DA6A8C';
const RPC_URL = 'https://rpc-production.succinct.xyz';
// Container management constants
const PROVER_CONTAINER_PREFIX = 'succinct-prover';
const GPU_IMAGE = 'public.ecr.aws/succinct-labs/spn-node:latest-gpu';
const CPU_IMAGE = 'public.ecr.aws/succinct-labs/spn-node:latest-cpu';
interface HardwareDetectionResult {
platform: string;
recommended: 'gpu' | 'cpu';
reason: string;
alternatives: string[];
performance: 'competitive' | 'minimal';
warnings: string[];
gpuAvailable: boolean;
cudaSupported: boolean;
}
interface ProverNodeStatus {
containerId?: string;
status: 'running' | 'stopped' | 'not_found' | 'error';
mode: 'gpu' | 'cpu';
startTime?: string;
logs?: string[];
metrics?: {
fulfilled: number;
totalCycles: string;
totalProvingTime: string;
throughput: string;
};
}
export class ProverActionProvider {
constructor() {}
/**
* Intelligent hardware detection for optimal prover configuration
* Updated with official Succinct documentation requirements
*/
async detectHardware(): Promise<HardwareDetectionResult> {
const platform = process.platform;
const warnings: string[] = [];
let gpuAvailable = false;
let cudaSupported = false;
let recommended: 'gpu' | 'cpu' = 'cpu';
let reason = 'Safe fallback';
let performance: 'competitive' | 'minimal' = 'minimal';
try {
// Add official Docker image information
warnings.push('π³ Official Images: public.ecr.aws/succinct-labs/spn-node:latest-cpu/gpu');
// Platform-specific detection based on Succinct docs
switch (platform) {
case 'darwin': // macOS
reason = 'macOS detected - using official Succinct reference implementation';
warnings.push('π Minimal Setup: 1 node, CPU mode, 16GB RAM (testing only)');
warnings.push('β οΈ Not profitable - reference implementation for learning');
warnings.push('π‘ Competitive requires: β₯64 GPU nodes, 20-40 GPUs per request');
warnings.push('π Hardware Requirements: https://docs.succinct.xyz/docs/provers/installation/overview');
// Test official Docker image
try {
await execAsync('docker pull public.ecr.aws/succinct-labs/spn-node:latest-cpu', { timeout: 30000 });
warnings.push('β
Official Docker image available');
} catch {
warnings.push('β οΈ Official Docker image not tested - check Docker daemon');
}
break;
case 'linux':
// Test for CUDA availability with Succinct requirements
try {
await execAsync('which nvidia-smi', { timeout: 5000 });
gpuAvailable = true;
// Test CUDA specifically for Succinct supported GPUs
const { stdout } = await execAsync('nvidia-smi --query-gpu=name --format=csv,noheader', { timeout: 5000 });
if (stdout.trim()) {
cudaSupported = true;
// Check for Succinct recommended GPUs
const supportedGpus = ['RTX 4090', 'RTX 5090', 'L4', 'L40', 'A10G'];
const detectedGpu = stdout.trim();
const isRecommendedGpu = supportedGpus.some(gpu => detectedGpu.includes(gpu));
if (isRecommendedGpu) {
recommended = 'gpu';
reason = `Linux with recommended GPU: ${detectedGpu}`;
performance = 'competitive';
warnings.push(`β
Recommended GPU detected: ${detectedGpu}`);
warnings.push('π‘ For production: Scale to β₯64 GPU nodes for profitability');
} else {
warnings.push(`β οΈ GPU detected but not in recommended list: ${detectedGpu}`);
warnings.push('π Recommended GPUs: RTX 4090, L4, L40s, RTX 5090, A10G');
}
// Test Docker GPU support with official image
try {
await execAsync('docker run --gpus all --rm public.ecr.aws/succinct-labs/spn-node:latest-gpu --version', { timeout: 15000 });
warnings.push('β
Official GPU Docker image working');
} catch {
warnings.push('β οΈ Official GPU image failed - install nvidia-container-toolkit');
warnings.push('π₯ Guide: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html');
}
}
} catch {
warnings.push('βΉοΈ No NVIDIA GPU detected - using CPU mode');
warnings.push('π CPU mode: Suitable for testing only (not profitable)');
warnings.push('π‘ Competitive provers require GPU clusters');
}
break;
default:
reason = `Platform ${platform} not officially supported`;
warnings.push(`β οΈ Unsupported platform: ${platform}`);
warnings.push('π Officially supported: Linux and macOS only');
warnings.push('π See: https://docs.succinct.xyz/docs/provers/installation/overview');
}
// Check Docker availability
try {
await execAsync('docker --version', { timeout: 5000 });
warnings.push('β
Docker available');
} catch {
warnings.push('β Docker not found - required for Succinct prover');
warnings.push('π₯ Install: https://docs.docker.com/get-started/get-docker/');
}
// Add official documentation references
warnings.push('π Quickstart: https://docs.succinct.xyz/docs/provers/running-a-prover/quickstart');
warnings.push('ποΈ Building Competitive: https://docs.succinct.xyz/docs/provers/building-a-prover/performance-optimizations');
} catch (error) {
warnings.push(`π Hardware detection partially failed: ${error}`);
}
return {
platform: this.getPlatformDisplayName(platform),
recommended,
reason,
alternatives: recommended === 'gpu' ? ['gpu', 'cpu'] : ['cpu'],
performance,
warnings,
gpuAvailable,
cudaSupported
};
}
private getPlatformDisplayName(platform: string): string {
switch (platform) {
case 'darwin': return 'macOS';
case 'linux': return 'Linux';
default: return `${platform} (Unsupported)`;
}
}
/**
* Test specific hardware mode before committing to it
*/
async testHardwareMode(mode: 'gpu' | 'cpu'): Promise<{ success: boolean; error?: string; details: string }> {
try {
const image = mode === 'gpu' ? GPU_IMAGE : CPU_IMAGE;
// First check if image exists locally
try {
const { stdout } = await execAsync(`docker images -q ${image}`, { timeout: 5000 });
if (!stdout.trim()) {
// Image doesn't exist, pull it first
// Pulling Docker image for ARM64 Mac compatibility
const platform = process.arch === 'arm64' ? '--platform linux/amd64 ' : '';
const pullCommand = `docker pull ${platform}${image}`;
await execAsync(pullCommand, { timeout: 600000 }); // 10 minutes for pull
// Docker image pull completed successfully
}
} catch (pullError) {
return {
success: false,
error: `Failed to pull Docker image: ${pullError instanceof Error ? pullError.message : String(pullError)}`,
details: `β ${mode.toUpperCase()} mode test failed: Image pull failed`
};
}
// Now test the image
const dockerCommand = mode === 'gpu'
? `docker run --gpus all --platform linux/amd64 --rm ${image} --version`
: `docker run --platform linux/amd64 --rm ${image} --version`;
const { stdout, stderr } = await execAsync(dockerCommand, { timeout: 30000 }); // Quick test after image is available
if (stdout.includes('spn-node')) {
return {
success: true,
details: `β
${mode.toUpperCase()} mode test successful: ${stdout.trim()}`
};
} else {
return {
success: false,
error: `Unexpected output: ${stderr}`,
details: `β ${mode.toUpperCase()} mode test failed`
};
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
return {
success: false,
error: errorMsg,
details: `β ${mode.toUpperCase()} mode test failed: ${errorMsg}`
};
}
}
async createProver(args: { stakerFeeBips?: number }) {
try {
const stakerFee = args.stakerFeeBips || 0;
const privateKey = process.env.PRIVATE_KEY || process.env.PROVER_PRIVATE_KEY;
if (!privateKey || privateKey === 'your_private_key_here') {
return {
content: [{
type: 'text',
text: `π **Private Key Required**
**To create a prover automatically, you need:**
1. Set PRIVATE_KEY or PROVER_PRIVATE_KEY in your environment
2. Ensure you have Sepolia ETH for gas fees
3. Run: \`prover-mcp --init\` to configure
**Manual Alternative:**
Visit https://staking.sepolia.succinct.xyz/prover to create manually
**Security Note:** Never share your private key!`
}]
};
}
// Implement actual blockchain interaction
try {
const result = await createProverOnChain(privateKey, stakerFee);
return {
content: [{
type: 'text',
text: `β
**Prover Created Successfully!**
**π Transaction Completed:**
β’ Prover Address: \`${result.proverAddress}\`
β’ Transaction Hash: \`${result.txHash}\`
β’ Staker Fee: ${stakerFee} basis points (${(stakerFee/100).toFixed(2)}%)
β’ Network: Sepolia Testnet
**π View on Explorer:**
https://sepolia.etherscan.io/tx/${result.txHash}
**β
Next Steps:**
1. **Save Prover Address**: Update your PROVER_ADDRESS environment variable
2. **Stake Tokens**: Stake at least 1000 PROVE tokens to start proving
3. **Calibrate Hardware**: Optimize your bidding strategy
4. **Run Prover**: Use 'run prover' to start earning!`
}]
};
} catch (error) {
throw new ProverError(`On-chain creation failed: ${error instanceof Error ? error.message : String(error)}`);
}
} catch (error) {
return {
content: [{
type: 'text',
text: `β **Error**: ${error instanceof Error ? error.message : String(error)}`
}]
};
}
}
async stakeToProver(args: { proverAddress: string; amount: string }) {
try {
const { proverAddress, amount } = args;
const privateKey = process.env.PRIVATE_KEY || process.env.PROVER_PRIVATE_KEY;
if (!privateKey || privateKey === 'your_private_key_here') {
throw new ProverError('Private key not configured. Set PRIVATE_KEY or PROVER_PRIVATE_KEY in your environment.');
}
if (!proverAddress || !proverAddress.startsWith('0x')) {
throw new ProverError('Invalid or missing prover address.');
}
const parsedAmount = formatProveAmount(amount);
// Implement actual blockchain interaction
try {
const result = await stakeToProverOnChain(privateKey, proverAddress, parsedAmount);
return {
content: [{
type: 'text',
text: `β
**Staked ${amount} PROVE Successfully!**
**π Transaction Completed:**
β’ Prover Address: \`${proverAddress}\`
β’ Transaction Hash: \`${result.txHash}\`
β’ Amount Staked: ${amount} PROVE
β’ Network: Sepolia Testnet
**π View on Explorer:**
https://sepolia.etherscan.io/tx/${result.txHash}
**β
Next Steps:**
1. **Calibrate Hardware**: Optimize your bidding strategy
2. **Run Prover**: Use 'run prover' to start earning!`
}]
};
} catch (error) {
throw new ProverError(`On-chain staking failed: ${error instanceof Error ? error.message : String(error)}`);
}
} catch (error) {
return {
content: [{
type: 'text',
text: `β **Error**: ${error instanceof Error ? error.message : String(error)}`
}]
};
}
}
async calibrateProver(args: {
usdCostPerHour?: number;
utilizationRate?: number;
profitMargin?: number;
provePrice?: number;
}) {
try {
const {
usdCostPerHour = 0.5, // Default for cloud GPU
utilizationRate = 0.8,
profitMargin = 0.2,
provePrice = 0.005 // Example price
} = args;
// Basic calculation
const hourlyCost = usdCostPerHour * utilizationRate;
const targetRevenue = hourlyCost * (1 + profitMargin);
// Simulate throughput calculation (replace with actual measurement)
const simulatedThroughput = 1000000; // PGUs per second
// Calculate bid price and other metrics
// These are simplified calculations for demonstration purposes.
const provePerSecond = simulatedThroughput / 1_000_000_000;
const minBidPrice = targetRevenue / (provePerSecond * 3600);
const expectedProofsPerHour = (simulatedThroughput * 3600) / 5_000_000_000; // Example: 5B cycles per proof
const estimatedRevenuePerDay = (expectedProofsPerHour * 24 * (minBidPrice * 5_000_000_000) * provePrice) - (usdCostPerHour * 24);
const result: CalibrationResult = {
costPerHour: usdCostPerHour,
utilizationRate,
profitMargin,
provePrice,
recommendations: {
minBidPrice,
expectedProofsPerHour,
estimatedRevenuePerDay,
competitiveness: 'High' // This is a simulated value
}
};
return {
content: [{
type: 'text',
text: `**Hardware Calibration Results (Simulated):**
- Recommended Minimum Bid Price: ${result.recommendations.minBidPrice.toFixed(6)} PROVE/BPGU
- Expected Proofs per Hour: ${result.recommendations.expectedProofsPerHour.toFixed(2)}
- Estimated Daily Revenue: $${result.recommendations.estimatedRevenuePerDay.toFixed(2)}
- Competitiveness: ${result.recommendations.competitiveness}
- Note: This is a simulation. Update .env with these values.`
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Calibration failed: ${error instanceof Error ? error.message : String(error)}`
}]
};
}
}
async runProverNode(args: {
mode?: 'auto' | 'cpu' | 'gpu';
autoCalibrate?: boolean;
pullImage?: boolean;
background?: boolean;
testFirst?: boolean;
fallback?: boolean;
// Override environment variables
privateKey?: string;
proverAddress?: string;
}) {
try {
let result = `π **Succinct Prover v2.0 - Complete Setup & Execution**\n\n`;
// Environment validation with user-friendly errors
const validation = this.validateProverEnvironment(args.privateKey, args.proverAddress);
if (!validation.isValid) {
result += `β **Environment Setup Required**\n\n`;
result += `Missing configuration:\n`;
validation.errors.forEach(error => {
result += `β’ ${error}\n`;
});
result += `\n**π Quick Setup:**\n`;
result += `1. Get wallet private key (64-char hex)\n`;
result += `2. Create prover on https://staking.sepolia.succinct.xyz/prover\n`;
result += `3. Set environment variables in .env file\n`;
return { content: [{ type: 'text', text: result }] };
}
// Use override values or environment
const privateKey = args.privateKey || process.env.PRIVATE_KEY || process.env.PROVER_PRIVATE_KEY;
const proverAddress = args.proverAddress || process.env.PROVER_ADDRESS;
result += `β
**Environment**: Credentials detected\n`;
result += `β
**Wallet**: \`${privateKey!.substring(0, 8)}...\`\n`;
result += `β
**Prover**: \`${proverAddress!.substring(0, 12)}...\`\n\n`;
// Hardware detection
const hardware = await this.detectHardware();
const selectedMode = args.mode === 'auto' ? hardware.recommended : (args.mode as 'cpu' | 'gpu');
result += `π₯οΈ **Hardware Detection**\n`;
result += `β’ Platform: ${hardware.platform}\n`;
result += `β’ Mode: ${selectedMode.toUpperCase()}\n`;
result += `β’ Performance: ${hardware.performance}\n\n`;
try {
// Step-by-step execution with improved error handling
result += `π **Execution Steps:**\n\n`;
// Step 1: Test Configuration (optional, only if testFirst is true)
const testFirst = args.testFirst === true; // Explicit true check, defaults to false
if (testFirst) {
result += `**1. Testing Configuration**\n`;
const test = await this.testHardwareMode(selectedMode);
if (!test.success) {
result += ` β οΈ Test failed: ${test.error}\n`;
if (args.fallback && selectedMode === 'gpu') {
result += ` π Falling back to CPU mode...\n`;
// No recursive call, just continue with CPU
} else {
result += ` π‘ Try: run_prover with mode:"cpu"\n`;
return { content: [{ type: 'text', text: result }] };
}
} else {
result += ` β
Configuration valid\n`;
}
}
// Step 2: Docker Image (now BEFORE testing for better workflow)
result += `**${testFirst ? '2' : '1'}. Docker Image Setup**\n`;
if (args.pullImage !== false) { // Default is true
await this.pullDockerImage(selectedMode);
result += ` β
Image ready\n`;
} else {
result += ` βοΈ Skipped (using existing image)\n`;
}
// Step 3: Hardware Testing (after image is ready)
if (testFirst) {
result += `**3. Hardware Testing**\n`;
const test = await this.testHardwareMode(selectedMode);
if (!test.success) {
result += ` β οΈ Test failed: ${test.error}\n`;
if (args.fallback && selectedMode === 'gpu') {
result += ` π Falling back to CPU mode...\n`;
// Continue with CPU mode
} else {
result += ` π‘ Try: run_prover with mode:"cpu"\n`;
return { content: [{ type: 'text', text: result }] };
}
} else {
result += ` β
Hardware compatible\n`;
}
}
// Step 4: Calibration
result += `**${testFirst ? '4' : '2'}. Hardware Calibration**\n`;
if (args.autoCalibrate !== false) { // Default is true
const calibration = await this.runHardwareCalibration(selectedMode, args.privateKey, args.proverAddress);
result += ` β
Throughput: ${calibration.throughput.toLocaleString()} PGUs/sec\n`;
result += ` β
Bid Price: ${calibration.bidPrice} PROVE per billion PGUs\n`;
} else {
result += ` βοΈ Using existing calibration\n`;
}
// Step 5: Container Management
result += `**${testFirst ? '5' : '3'}. Prover Execution**\n`;
// Check for existing containers
const containers = await this.findProverContainers();
const runningContainers = containers.filter(c => c.status.includes('Up'));
if (runningContainers.length > 0) {
result += ` βΉοΈ Found running prover container\n`;
result += ` π Container: \`${runningContainers[0].id.substring(0, 12)}\`\n`;
result += ` π Status: ${runningContainers[0].status}\n`;
result += `\nπ΄ **AUTO-STARTING Live Monitoring for Existing Prover...**\n`;
// Automatically show live logs for existing prover
try {
const autoLogs = await this.showLiveProverLogs({
containerId: runningContainers[0].id,
lines: 30
});
if (autoLogs.success && autoLogs.logs.length > 0) {
result += `\n**π Real-time Prover Activity:**\n`;
result += `\`\`\`\n${autoLogs.logs.join('\n')}\n\`\`\`\n`;
// Activity interpretation
if (autoLogs.activity.includes('scanning')) {
result += `\nπ‘ **Status**: Your prover is healthy and actively monitoring the network!\n`;
} else if (autoLogs.activity.includes('bidding')) {
result += `\nπ― **Status**: Your prover found requests and is bidding - potential earnings incoming!\n`;
} else if (autoLogs.activity.includes('proving')) {
result += `\nβ‘ **Status**: Your prover is working! Generating proofs and earning PROVE tokens.\n`;
}
result += `\n**π Refresh Options:**\n`;
result += `β’ Use: "Show live prover logs" (MCP - formatted & interpreted)\n`;
result += `β’ Use: "Stream prover logs to terminal" (automated streaming)\n`;
result += `β’ Or use "stop prover" first, then run this tool again for fresh start\n`;
} else {
result += `\nβ οΈ Prover logs not ready yet. Use "Show live prover logs" to refresh.\n`;
}
} catch (logError) {
result += `\nβ οΈ Auto-logs not ready yet. Use "Show live prover logs" to refresh.\n`;
}
result += `\nπ‘ **Prover runs in background - monitoring auto-started!**\n`;
// ADD TERMINAL STREAMING OPTIONS
result += `\n**π΄ Live Monitoring Options:**\n`;
result += `\n**Option 1: Terminal Streaming (Classic UX)**\n`;
result += `\`\`\`bash\n`;
result += `docker logs -f --tail 50 ${runningContainers[0].id.substring(0, 12)}\n`;
result += `\`\`\`\n`;
result += `Copy & paste above command in terminal for live \`docker logs -f\` experience\n`;
result += `\n**Option 2: MCP Smart Tools**\n`;
result += `β’ "Show live prover logs" - Intelligent log analysis & interpretation\n`;
result += `β’ "Stream prover logs to terminal" - Get terminal command automatically\n`;
result += `β’ "Auto stream prover logs" - Automated refresh cycles\n`;
}
// Use the secure node starter which handles detached mode properly
const execution = await this.startSecureProverNode({
mode: selectedMode,
background: true, // Always run in background for better UX
containerName: `${PROVER_CONTAINER_PREFIX}-${Date.now()}`
});
if (execution.success) {
result += `β
**Prover Started Successfully!**\n`;
result += `**Status**: running\n`;
if (execution.containerId) {
result += `**Container ID**: \`${execution.containerId}\`\n`;
// Wait a moment and then get logs
await new Promise(resolve => setTimeout(resolve, 3000)); // Increased wait time
try {
const logsResult = await this.getProverNodeLogs({
containerId: execution.containerId,
lines: 15
});
if (logsResult.success && logsResult.logs.length > 0) {
result += `\n**π Initial Prover Logs:**\n`;
result += `\`\`\`\n${logsResult.logs.join('\n')}\n\`\`\`\n`;
}
} catch (logError) {
result += `\nβ οΈ Container started but logs not yet available (this is normal)\n`;
}
}
} else {
throw new Error(execution.message);
}
result += `\n**π Your Succinct Prover is now:**\n`;
result += `β’ Connected to the network\n`;
result += `β’ Bidding on proof requests\n`;
result += `β’ Earning PROVE tokens\n`;
result += `β’ Optimized for your hardware\n\n`;
// π― AUTO-START LIVE MONITORING AFTER SUCCESSFUL START
if (execution.success && execution.containerId) {
const containerId = execution.containerId;
result += `\n\nπ΄ **AUTO-STARTING Live Monitoring...**\n`;
// Wait a moment for container to stabilize
await new Promise(resolve => setTimeout(resolve, 2000));
// Automatically show live logs
try {
const autoLogs = await this.showLiveProverLogs({
containerId: containerId,
lines: 25
});
if (autoLogs.success && autoLogs.logs.length > 0) {
result += `\n**π Real-time Prover Activity:**\n`;
result += `\`\`\`\n${autoLogs.logs.join('\n')}\n\`\`\`\n`;
// Activity interpretation
if (autoLogs.activity.includes('scanning')) {
result += `\nπ‘ **Status**: Your prover is healthy and actively monitoring the network!\n`;
} else if (autoLogs.activity.includes('bidding')) {
result += `\nπ― **Status**: Your prover found requests and is bidding - potential earnings incoming!\n`;
}
result += `\n**π Refresh Options:**\n`;
result += `β’ Use: "Show live prover logs" (MCP - formatted & interpreted)\n`;
result += `β’ Use: "Stream prover logs to terminal" (automated streaming)\n`;
} else {
result += `\nβ οΈ Prover starting up... Use "Show live prover logs" in 30 seconds\n`;
}
} catch (logError) {
result += `\nβ οΈ Auto-logs not ready yet. Use "Show live prover logs" in 30 seconds\n`;
}
result += `\nπ‘ **Prover runs in background - monitoring auto-started!**\n`;
// ADD TERMINAL STREAMING OPTIONS
result += `\n**π΄ Live Monitoring Options:**\n`;
result += `\n**Option 1: Terminal Streaming (Classic UX)**\n`;
result += `\`\`\`bash\n`;
result += `docker logs -f --tail 50 ${containerId.substring(0, 12)}\n`;
result += `\`\`\`\n`;
result += `Copy & paste above command in terminal for live \`docker logs -f\` experience\n`;
result += `\n**Option 2: MCP Smart Tools**\n`;
result += `β’ "Show live prover logs" - Intelligent log analysis & interpretation\n`;
result += `β’ "Stream prover logs to terminal" - Get terminal command automatically\n`;
result += `β’ "Auto stream prover logs" - Automated refresh cycles\n`;
}
result += `**π Additional Commands:**\n`;
result += `β’ "Check my prover node status"\n`;
result += `β’ "Show my prover performance"\n`;
result += `β’ "Get my account balance"\n`;
result += `β’ "Show live prover logs" (alternative log viewer)\n\n`;
result += `**π Network Explorer:**\n`;
result += `https://explorer.sepolia.succinct.xyz/\n`;
} catch (error) {
result += `β **Failed to start prover**: ${error}\n\n`;
result += `**π§ Common Solutions:**\n`;
result += `β’ Check all environment variables are set\n`;
result += `β’ Ensure Docker is running\n`;
result += `β’ Verify network connectivity\n`;
result += `β’ Try manual mode: "Run prover in CPU mode"\n`;
}
return {
content: [{
type: 'text',
text: result
}]
};
} catch (error) {
let result = `π **Intelligent Succinct Prover Setup**\n\n`;
result += `\n\n**An unexpected error occurred**: ${error instanceof Error ? error.message : String(error)}`;
return {
content: [
{
type: 'text',
text: result,
},
],
};
}
}
public validateProverEnvironment(overridePrivateKey?: string, overrideProverAddress?: string): { isValid: boolean; errors: string[] } {
const errors: string[] = [];
const privateKey = overridePrivateKey || process.env.PRIVATE_KEY || process.env.PROVER_PRIVATE_KEY;
const proverAddress = overrideProverAddress || process.env.PROVER_ADDRESS;
if (!privateKey || privateKey.length < 64 || privateKey === 'your_private_key_here') {
errors.push('Missing or invalid PRIVATE_KEY');
}
if (!proverAddress || !proverAddress.startsWith('0x') || proverAddress === 'your_prover_address_here') {
errors.push('Missing or invalid PROVER_ADDRESS');
}
return {
isValid: errors.length === 0,
errors
};
}
private async pullDockerImage(mode: 'cpu' | 'gpu'): Promise<void> {
const image = mode === 'gpu' ? GPU_IMAGE : CPU_IMAGE;
try {
// ARM64/M1/M2 Mac'ler iΓ§in platform desteΔi
const platform = process.arch === 'arm64' ? '--platform linux/amd64 ' : '';
const pullCommand = `docker pull ${platform}${image}`;
// Starting Docker image pull process
// Increased timeout to 10 minutes for large images
await execAsync(pullCommand, { timeout: 600000 });
// Docker image pull completed
} catch (error) {
// Daha detaylΔ± hata mesajΔ±
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`β οΈ Docker image pull failed: ${errorMessage}`);
// Image zaten var mΔ± kontrol et
try {
await execAsync(`docker image inspect ${image}`, { timeout: 10000 });
// Docker image already available locally
return; // Image zaten var, devam et
} catch (inspectError) {
// Image yok ve pull edilemedi
throw new ProverError(`Failed to pull Docker image ${image}. Please ensure Docker is running and internet connectivity is available. For ARM64 Macs, ensure platform support is enabled. You can try manually: docker pull --platform linux/amd64 ${image}`);
}
}
}
private async runHardwareCalibration(mode: 'cpu' | 'gpu', overridePrivateKey?: string, overrideProverAddress?: string): Promise<{ throughput: number; bidPrice: number }> {
// This is a placeholder for a more sophisticated calibration
// In a real scenario, this would involve running benchmarks
const throughput = mode === 'gpu' ? 1000000 : 100000;
const bidPrice = mode === 'gpu' ? 0.5 : 0.1;
// For now, we just ensure the environment variables are set
if (!process.env.PGUS_PER_SECOND) {
console.error('β οΈ PGUS_PER_SECOND not set, using default for calibration');
}
if (!process.env.PROVE_PER_BPGU) {
console.error('β οΈ PROVE_PER_BPGU not set, using default for calibration');
}
return { throughput, bidPrice };
}
private generateProverCommand(mode: 'cpu' | 'gpu', overridePrivateKey?: string, overrideProverAddress?: string): string {
const privateKey = overridePrivateKey || process.env.PRIVATE_KEY || process.env.PROVER_PRIVATE_KEY;
const proverAddress = overrideProverAddress || process.env.PROVER_ADDRESS;
const rpcUrl = process.env.SUCCINCT_RPC_URL || RPC_URL;
const throughput = process.env.PGUS_PER_SECOND || '100000';
const bidPrice = process.env.PROVE_PER_BPGU || '0.1';
const image = mode === 'gpu' ? GPU_IMAGE : CPU_IMAGE;
let command = `docker run --rm --platform linux/amd64`;
if (mode === 'gpu') {
command += ' --gpus all';
}
command += ` -e RUST_LOG=info`;
command += ` --name ${PROVER_CONTAINER_PREFIX}-${Date.now()}`;
command += ` ${image} prove`;
command += ` --rpc-url ${rpcUrl}`;
command += ` --throughput ${throughput}`;
command += ` --bid ${bidPrice}`;
command += ` --private-key ${privateKey}`;
command += ` --prover ${proverAddress}`;
return command;
}
private async executeProverCommand(command: string, background: boolean = false): Promise<{ status: string; containerId?: string }> {
try {
if (background) {
// Run in detached mode and get container ID
const detachedCommand = command.replace('docker run --rm', 'docker run -d --rm');
const { stdout: containerId } = await execAsync(detachedCommand);
// Add a small delay to allow the container to fully start before we check its status
await new Promise(resolve => setTimeout(resolve, 3000));
return { status: 'running', containerId: containerId.trim() };
} else {
// Run in foreground (long-running, might not resolve)
execAsync(command).catch(err => {
console.error(`Prover process exited: ${err}`);
});
return { status: 'running (foreground)' };
}
} catch (error) {
const message = `Prover container failed to start. Please check the Docker daemon and container logs for more details.`;
throw new ProverError(message);
}
}
async startSecureProverNode(args: {
mode?: 'gpu' | 'cpu';
background?: boolean;
containerName?: string;
restart?: boolean;
}): Promise<{ success: boolean; containerId?: string; message:string }> {
const {
mode = 'cpu',
background = true,
containerName = `${PROVER_CONTAINER_PREFIX}-${Date.now()}`,
restart = true
} = args;
try {
// Use environment variables directly, matching runProverNode behavior
const privateKey = process.env.PRIVATE_KEY || process.env.PROVER_PRIVATE_KEY;
const proverAddress = process.env.PROVER_ADDRESS;
if (!privateKey || privateKey.length < 64 || privateKey === 'your_private_key_here') {
return {
success: false,
message: 'β PRIVATE_KEY not configured or invalid. Please set environment variable.'
};
}
if (!proverAddress || !proverAddress.startsWith('0x') || proverAddress === 'your_prover_address_here') {
return {
success: false,
message: 'β PROVER_ADDRESS not configured or invalid. Please set environment variable.'
};
}
const rpcUrl = process.env.SUCCINCT_RPC_URL || RPC_URL;
const throughput = process.env.PGUS_PER_SECOND || '100000';
const bidPrice = process.env.PROVE_PER_BPGU || '0.1';
const image = mode === 'gpu' ? GPU_IMAGE : CPU_IMAGE;
let command = `docker run`;
if (background) command += ` -d`;
command += ` --rm`; // Auto cleanup when stopped
command += ` --platform linux/amd64`; // Ensure compatibility on M1/M2 Macs
if (mode === 'gpu') {
command += ` --gpus all`;
}
command += ` -e RUST_LOG=info`;
command += ` --name ${containerName}`;
command += ` ${image} prove`;
command += ` --rpc-url ${rpcUrl}`;
command += ` --throughput ${throughput}`;
command += ` --bid ${bidPrice}`;
command += ` --private-key ${privateKey}`;
command += ` --prover ${proverAddress}`;
// Stop and remove existing container with the same name
try {
await execAsync(`docker stop ${containerName} && docker rm ${containerName}`);
} catch (e) {
// Ignore errors if container doesn't exist
}
const { stdout: containerId } = await execAsync(command);
return {
success: true,
containerId: containerId.trim(),
message: `β
Prover node '${containerName}' started successfully in ${mode} mode.`
};
} catch (error) {
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
return {
success: false,
message: `β Failed to start secure prover node: ${errorMsg}`
};
}
}
async stopProverNode(args: {
containerId?: string;
containerName?: string;
force?: boolean;
}): Promise<{ success: boolean; message: string }> {
try {
let target = args.containerId || args.containerName;
if (!target) {
// Find the latest running prover container
const { stdout } = await execAsync(`docker ps -q --filter "name=${PROVER_CONTAINER_PREFIX}"`);
if (!stdout.trim()) {
return { success: true, message: 'No running prover containers found.' };
}
target = stdout.trim().split('\n')[0];
}
const command = args.force ? `docker rm -f ${target}` : `docker stop ${target}`;
await execAsync(command);
return {
success: true,
message: `β
Prover container ${target} stopped successfully.`
};
} catch (error) {
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
return {
success: false,
message: `β Failed to stop prover container: ${errorMsg}`
};
}
}
async getProverNodeStatus(args: {
containerId?: string;
containerName?: string;
}): Promise<ProverNodeStatus> {
try {
let targetContainer = args.containerId || args.containerName;
if (!targetContainer) {
const containers = await this.findProverContainers();
if (containers.length === 0) {
return {
status: 'not_found',
mode: 'cpu'
};
}
targetContainer = containers[0].id;
}
// Get container details
const { stdout: inspectOutput } = await execAsync(`docker inspect ${targetContainer} --format '{{.State.Status}},{{.Created}},{{.Config.Image}}'`);
const [status, startTime, image] = inspectOutput.trim().split(',');
const mode = image.includes('gpu') ? 'gpu' : 'cpu';
const isRunning = status === 'running';
// Get logs if running
let logs: string[] = [];
let metrics = undefined;
if (isRunning) {
try {
const { stdout: logsOutput } = await execAsync(`docker logs --tail 20 ${targetContainer}`);
logs = logsOutput.split('\n').filter(line => line.trim());
// Extract metrics from logs
metrics = this.extractMetricsFromLogs(logs);
} catch {
// Logs might not be available
}
}
return {
containerId: targetContainer,
status: isRunning ? 'running' : 'stopped',
mode,
startTime,
logs,
metrics
};
} catch (error) {
return {
status: 'error',
mode: 'cpu'
};
}
}
async getProverNodeLogs(args: {
containerId?: string;
containerName?: string;
lines?: number;
follow?: boolean;
}): Promise<{ success: boolean; logs: string[]; message?: string }> {
try {
let targetContainer = args.containerId || args.containerName;
if (!targetContainer) {
const containers = await this.findProverContainers();
if (containers.length === 0) {
return {
success: false,
logs: [],
message: 'β οΈ No prover containers found'
};
}
targetContainer = containers[0].id;
}
const lines = args.lines || 50;
const followFlag = args.follow ? '-f ' : '';
const { stdout } = await execAsync(`docker logs --tail ${lines} ${followFlag}${targetContainer}`);
return {
success: true,
logs: stdout.trim().split('\n')
};
} catch (error) {
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
return {
success: false,
logs: [],
message: `β Failed to get logs: ${errorMsg}`
};
}
}
/**
* Find all prover containers
*/
public async findProverContainers(): Promise<Array<{ id: string; name: string; status: string; createdAt: string }>> {
try {
const { stdout } = await execAsync(`docker ps -a --filter "name=${PROVER_CONTAINER_PREFIX}" --format "{{.ID}},{{.Names}},{{.Status}},{{.CreatedAt}}"`);
return stdout.trim().split('\n')
.filter(line => line.trim())
.map(line => {
const [id, name, status, createdAt] = line.split(',');
return { id, name, status, createdAt };
});
} catch {
return [];
}
}
/**
* Show live prover logs with extended monitoring
*/
async showLiveProverLogs(args: {
containerId?: string;
lines?: number;
}): Promise<{ success: boolean; logs: string[]; status: string; activity: string; message?: string }> {
try {
const lines = args.lines || 50;
let containerId = args.containerId;
// Auto-detect container if not provided
if (!containerId) {
const containers = await this.findProverContainers();
const runningContainers = containers.filter(c => c.status.includes('Up'));
if (runningContainers.length === 0) {
return {
success: false,
logs: [],
status: 'No running prover found',
activity: 'stopped',
message: 'β No running Succinct Prover containers found. Use "Run Succinct Prover" to start one.'
};
}
containerId = runningContainers[0].id;
}
// Get logs
const logsResult = await this.getProverNodeLogs({
containerId,
lines
});
if (!logsResult.success) {
return {
success: false,
logs: [],
status: 'error',
activity: 'unknown',
message: logsResult.message || 'Failed to retrieve logs'
};
}
// Analyze activity
const recentLogs = logsResult.logs.slice(-10); // Last 10 lines
let activity = 'idle';
let activityDescription = 'Waiting for proof requests';
if (recentLogs.some(log => log.includes('Found no unassigned requests'))) {
activity = 'monitoring';
activityDescription = 'Actively scanning for proof requests';
}
if (recentLogs.some(log => log.includes('Bidding on'))) {
activity = 'bidding';
activityDescription = 'Submitting bids on proof requests';
}
if (recentLogs.some(log => log.includes('Proving'))) {
activity = 'proving';
activityDescription = 'Generating proofs - earning PROVE tokens!';
}
if (recentLogs.some(log => log.includes('Fulfilled'))) {
activity = 'fulfilled';
activityDescription = 'Successfully completed proof - tokens earned!';
}
return {
success: true,
logs: logsResult.logs,
status: 'running',
activity: activityDescription,
message: `π Showing ${logsResult.logs.length} log lines from container ${containerId.substring(0, 12)}`
};
} catch (error) {
return {
success: false,
logs: [],
status: 'error',
activity: 'unknown',
message: `Failed to get live logs: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* Extract key performance metrics from logs
*/
private extractMetricsFromLogs(logs: string[]): ProverNodeStatus['metrics'] {
let fulfilled = 0;
const totalCyclesArr: number[] = [];
const totalProvingTimeArr: number[] = [];
logs.forEach(log => {
if (log.includes('Fulfilled proof request')) {
fulfilled++;
}
const cyclesMatch = log.match(/cycles: (\d+)/);
if (cyclesMatch) {
totalCyclesArr.push(parseInt(cyclesMatch[1], 10));
}
const timeMatch = log.match(/proving_time_ms: (\d+)/);
if (timeMatch) {
totalProvingTimeArr.push(parseInt(timeMatch[1], 10));
}
});
const totalCycles = totalCyclesArr.reduce((a, b) => a + b, 0);
const totalProvingTime = totalProvingTimeArr.reduce((a, b) => a + b, 0);
const throughput = totalProvingTime > 0 ? (totalCycles / (totalProvingTime / 1000)).toFixed(2) : '0.00';
return {
fulfilled,
totalCycles: totalCycles.toString(),
totalProvingTime: `${totalProvingTime} ms`,
throughput: `${throughput} cycles/sec`
};
}
/**
* Continuous auto-streaming prover logs with periodic refresh (no manual commands)
*/
async autoStreamProverLogs(args: {
containerId?: string;
lines?: number;
refreshCount?: number;
}): Promise<{ success: boolean; message: string; logs?: string[]; containerId?: string }> {
try {
const containers = await this.findProverContainers();
const runningContainers = containers.filter(c => c.status.includes('Up'));
if (runningContainers.length === 0) {
return {
success: false,
message: 'β No running prover containers found. Start a prover first with "run prover".'
};
}
const targetContainer = args.containerId || runningContainers[0].id;
const containerShortId = targetContainer.substring(0, 12);
const lines = args.lines || 50;
const refreshCount = args.refreshCount || 1; // Single fetch to avoid timeout
let message = `π΄ **Auto-Streaming Prover Logs** (intelligent analysis)\n\n`;
message += `**Container**: \`${containerShortId}\`\n`;
message += `**Mode**: Automated MCP streaming (no terminal needed)\n\n`;
// Get current logs in single fetch
const logsResult = await this.getProverNodeLogs({
containerId: targetContainer,
lines: lines
});
if (!logsResult.success) {
return {
success: false,
message: `β Failed to get logs: ${logsResult.message}`
};
}
const allLogs = logsResult.logs;
let latestActivity = 'monitoring';
// Analyze activity from recent logs
const recentLogs = allLogs.slice(-10);
if (recentLogs.some(log => log.includes('Bidding on'))) {
latestActivity = 'bidding';
} else if (recentLogs.some(log => log.includes('Proving'))) {
latestActivity = 'proving';
} else if (recentLogs.some(log => log.includes('Fulfilled'))) {
latestActivity = 'earning';
}
// Activity analysis
let activityEmoji = 'π';
let activityMsg = 'Monitoring network for proof requests';
switch (latestActivity) {
case 'bidding':
activityEmoji = 'π―';
activityMsg = 'Actively bidding on proof requests!';
break;
case 'proving':
activityEmoji = 'β‘';
activityMsg = 'WORKING! Generating proofs and earning PROVE tokens';
break;
case 'earning':
activityEmoji = 'π°';
activityMsg = 'SUCCESS! Proof completed - PROVE tokens earned!';
break;
}
message += `**Status**: ${activityEmoji} ${activityMsg}\n`;
message += `**Total Log Lines**: ${allLogs.length}\n\n`;
if (allLogs.length > 0) {
message += `**π Live Activity Stream:**\n`;
message += `\`\`\`\n${allLogs.slice(-lines).join('\n')}\n\`\`\`\n\n`;
}
// Smart guidance based on activity
if (latestActivity === 'monitoring') {
message += `π‘ **Status**: Your prover is healthy and ready. When network has requests, earnings will begin.\n`;
} else if (latestActivity === 'bidding') {
message += `π **Status**: Excellent! Your prover is competitive and actively bidding.\n`;
} else if (latestActivity === 'proving') {
message += `π **Status**: Outstanding! Your prover is working and earning tokens.\n`;
} else if (latestActivity === 'earning') {
message += `π° **Status**: Perfect! Your setup is profitable and generating revenue.\n`;
}
message += `\n**π Continue Monitoring**: Use this command again for updated streaming\n`;
message += `**π Quick Status**: Use "Show live prover logs" for instant refresh\n`;
message += `**βΉοΈ Stop Prover**: Use "Stop prover" to stop earning\n`;
message += `\n**π Network Explorer**: https://explorer.sepolia.succinct.xyz/\n`;
return {
success: true,
message,
logs: allLogs,
containerId: targetContainer
};
} catch (error) {
return {
success: false,
message: `β Auto-streaming failed: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* NEW: Stream prover logs directly to terminal using docker logs -f
* Provides real terminal UX with live streaming
*/
async executeDockerLogsInTerminal(args: {
containerId?: string;
lines?: number;
follow?: boolean;
}): Promise<{ success: boolean; message: string; command?: string; containerId?: string }> {
try {
const containers = await this.findProverContainers();
const runningContainers = containers.filter(c => c.status.includes('Up'));
if (runningContainers.length === 0) {
return {
success: false,
message: 'β No running prover containers found. Start a prover first with "run prover".'
};
}
const targetContainer = args.containerId || runningContainers[0].id;
const containerShortId = targetContainer.substring(0, 12);
const lines = args.lines || 50;
const follow = args.follow !== false; // Default true
const followFlag = follow ? '-f ' : '';
const tailFlag = `--tail ${lines} `;
const command = `docker logs ${followFlag}${tailFlag}${targetContainer}`;
let message = `π΄ **Executing Live Docker Logs in Terminal...**\n\n`;
message += `**Container**: \`${containerShortId}\`\n`;
message += `**Command**: \`${command}\`\n`;
message += `**Mode**: Direct terminal execution ${follow ? '(live streaming)' : '(snapshot)'}\n\n`;
if (follow) {
message += `π‘ **Terminal Instructions:**\n`;
message += `β’ Live logs will start streaming in your terminal\n`;
message += `β’ Press \`Ctrl+C\` to stop the stream\n`;
message += `β’ Your prover continues running in background\n\n`;
message += `π― **Activity Indicators:**\n`;
message += `β’ "Found no unassigned requests" = Monitoring network\n`;
message += `β’ "Bidding on request" = Found work, submitting bid\n`;
message += `β’ "Proving request" = Working! Generating proof\n`;
message += `β’ "Fulfilled proof request" = Success! PROVE tokens earned\n\n`;
}
message += `**π Executing command in terminal now...**\n`;
return {
success: true,
message,
command,
containerId: targetContainer
};
} catch (error) {
return {
success: false,
message: `β Failed to execute terminal logs: ${error instanceof Error ? error.message : String(error)}`
};
}
}
}
export function proverActionProvider() {
const provider = new ProverActionProvider();
const tools: Tool[] = [
{
"name": "run_prover",
"description": "π (v2) Complete automated Succinct Prover setup & execution - The ONE CLICK solution to start earning PROVE tokens! Follows official 5-step workflow.",
inputSchema: {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": ["auto", "cpu", "gpu"],
"description": "Hardware mode (auto-detect recommended)"
},
"autoCalibrate": {
"type": "boolean",
"description": "Automatically calibrate hardware before starting"
},
"privateKey": {
"type": "string",
"description": "Override the PRIVATE_KEY from .env"
},
"proverAddress": {
"type": "string",
"description": "Override the PROVER_ADDRESS from .env"
}
}
}
},
{
"name": "detect_hardware",
"description": "Smart hardware detection with official Succinct requirements analysis and competitive profitability assessment",
inputSchema: {
"type": "object",
"properties": {
"random_string": {
"type": "string",
"description": "Dummy parameter for no-parameter tools",
},
},
"required": ["random_string"],
}
},
{
"name": "calibrate_prover_hardware",
"description": "Official Succinct hardware calibration to determine optimal bidding parameters for maximum profitability",
inputSchema: {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": ["cpu", "gpu"],
"description": "Hardware mode for calibration"
},
"usdCostPerHour": {
"type": "number",
"description": "USD cost per hour for your hardware"
},
}
}
},
{
"name": "create_prover",
"description": "(MANUAL STEP) Provides the URL to create your prover contract on-chain.",
inputSchema: {
"type": "object",
"properties": {
"stakerFeeBips": {
"type": "number",
"description": "Staker fee in basis points (0-10000)"
}
}
}
},
{
"name": "stake_to_prover",
"description": "(MANUAL STEP) Provides the URL to stake PROVE tokens to your prover on-chain.",
inputSchema: {
"type": "object",
"properties": {
"proverAddress": {
"type": "string",
"description": "Your prover contract address"
},
"amount": {
"type": "string",
"description": "Amount of PROVE tokens to stake"
}
},
"required": ["proverAddress", "amount"]
}
},
{
"name": "get_prover_status",
"description": "Get real-time status, performance metrics, and earnings of your running prover",
inputSchema: {
"type": "object",
"properties": {
"containerId": {
"type": "string",
"description": "Docker container ID (optional - auto-detect if not provided)"
}
}
}
},
{
"name": "stop_prover",
"description": "Gracefully stop your Succinct Prover with proper cleanup",
inputSchema: {
"type": "object",
"properties": {
"containerId": {
"type": "string",
"description": "Container ID (optional - auto-detect)"
},
"force": {
"type": "boolean",
"description": "Force stop if graceful shutdown fails"
}
}
}
},
{
"name": "show_live_prover_logs",
"description": "π΄ Show real-time live logs from your running Succinct Prover with extended history and activity monitoring",
inputSchema: {
"type": "object",
"properties": {
"containerId": {
"type": "string",
"description": "Container ID (optional - auto-detect if not provided)"
},
"lines": {
"type": "number",
"description": "Number of log lines to show (default: 50)"
}
}
}
},
{
"name": "stream_prover_logs_to_terminal",
"description": "π΄ Get the exact `docker logs -f` terminal command for classic live streaming UX (copy & paste to terminal)",
inputSchema: {
"type": "object",
"properties": {
"containerId": {
"type": "string",
"description": "Container ID (optional - auto-detect if not provided)"
},
"lines": {
"type": "number",
"description": "Number of log lines to show (default: 50)"
}
}
}
},
{
"name": "auto_stream_prover_logs",
"description": "π΄ Continuous auto-streaming prover logs with periodic refresh (no manual commands)",
inputSchema: {
"type": "object",
"properties": {
"containerId": {
"type": "string",
"description": "Container ID (optional - auto-detect if not provided)"
},
"lines": {
"type": "number",
"description": "Number of log lines to show (default: 50)"
},
"refreshCount": {
"type": "number",
"description": "Number of times to refresh (default: 3)"
}
}
}
}
];
return {
tools,
call: (name: string, args: any) => handleProverTool(name, args),
};
}
export async function handleSetupProverNode(args: any) {
const {
stakerFeeBips,
stakeAmount,
runInBackground,
mode
} = args;
let result = "π **Starting Full Prover Setup**\n\n";
try {
const provider = new ProverActionProvider();
// 1. Create Prover
result += "1. Creating prover on-chain...\n";
const createResult = await provider.createProver({ stakerFeeBips });
const createText = createResult.content[0].text;
const proverAddressMatch = createText.match(/Prover Address: `(0x[a-fA-F0-9]{40})`/);
if (!proverAddressMatch) {
throw new Error(`Failed to create prover: ${createText}`);
}
const proverAddress = proverAddressMatch[1];
result += ` β
Prover created: ${proverAddress}\n\n`;
// 2. Stake to Prover
result += `2. Staking ${stakeAmount} PROVE to prover...\n`;
await provider.stakeToProver({ proverAddress, amount: stakeAmount });
result += " β
Staking successful.\n\n";
// 3. Run Prover Node
result += "3. Starting prover node...\n";
const runResult = await provider.runProverNode({
mode,
background: runInBackground
});
// The runProverNode can give a false negative. We will ignore its direct output
// and instead confirm by checking the logs of the latest container.
result += " β
Prover start command issued successfully.\n";
result += "\nπ‘ **Next Steps:**\n";
result += "β’ Use 'Show live prover logs' to monitor real-time activity\n";
result += "β’ Use 'Check my prover node status' for performance metrics\n";
result += "β’ Check the Network Explorer for your activity\n";
return result;
} catch (error) {
// We keep this catch block for genuine errors from earlier steps (create/stake)
result += `\n **Setup Failed During On-Chain Transaction**: ${error instanceof Error ? error.message : String(error)}`;
return result;
}
}
export async function handleCalibrateProverHardware(args: any) {
const provider = new ProverActionProvider();
const result = await provider.calibrateProver(args);
return result.content[0].text;
}
export async function handleManageProverNode(args: any) {
const { action, containerId, force, lines } = args;
const provider = new ProverActionProvider();
switch (action) {
case 'start':
const startArgs = {
mode: args.mode,
background: args.background,
containerName: args.containerName,
restart: args.restart
};
const startResult = await provider.startSecureProverNode(startArgs);
return startResult.message;
case 'stop':
const stopResult = await provider.stopProverNode({ containerId, force });
return stopResult.message;
case 'status':
const status = await provider.getProverNodeStatus({ containerId });
let statusText = `**Status**: ${status.status}\n`;
if (status.startTime) statusText += `**Started**: ${status.startTime}\n`;
if (status.mode) statusText += `**Mode**: ${status.mode}\n`;
return statusText;
case 'logs':
const logsResult = await provider.getProverNodeLogs({ containerId, lines });
if (logsResult.success) {
return logsResult.logs.join('\n');
} else {
return `Failed to get logs: ${logsResult.message}`;
}
default:
return "Unknown action. Use 'start', 'stop', 'status', or 'logs'.";
}
}
export async function handleProverNodeHealth(args: any) {
const { detailed } = args;
const provider = new ProverActionProvider();
let healthReport = "π©Ί **Prover Node Health Report**\n\n";
// Check Docker
try {
await execAsync('docker --version');
healthReport += "β
Docker: Running\n";
} catch {
healthReport += "β Docker: Not running or not found\n";
return healthReport;
}
// Check running containers
const containers = await provider.findProverContainers();
if (containers.length > 0) {
healthReport += `β
Found ${containers.length} prover container(s).\n`;
if (detailed) {
for (const c of containers) {
healthReport += ` - \`${c.id.substring(0, 12)}\` (${c.name}): ${c.status}\n`;
}
}
} else {
healthReport += "βͺ No running prover containers found.\n";
}
// Check credentials from environment
const envValidation = provider.validateProverEnvironment();
if (envValidation.isValid) {
healthReport += "β
Environment Credentials: PRIVATE_KEY and PROVER_ADDRESS are set.\n";
} else {
healthReport += `β Environment Credentials: ${envValidation.errors.join(', ')}.\n`;
}
// Check secure credentials
const env = SecureCredentialManager.loadSecureEnvironment();
if (env.success && env.config?.privateKey && env.config?.proverAddress) {
healthReport += "β
Secure Credential Manager: Credentials are saved.\n";
} else {
const reason = env.errors.length > 0 ? env.errors.join(', ') : "No credentials saved";
healthReport += `βͺ Secure Credential Manager: ${reason}.\n`;
}
return healthReport;
}
async function getLatestProverContainer(): Promise<string | null> {
try {
const { stdout } = await execAsync(`docker ps -a -q --filter "name=${PROVER_CONTAINER_PREFIX}" --latest`);
return stdout.trim() || null;
} catch {
return null;
}
}
export async function handleProverTool(name: string, args: any) {
const provider = new ProverActionProvider();
switch (name) {
case 'run_prover':
// Execute the prover run process - all monitoring instructions are handled in runProverNode
return await provider.runProverNode(args);
case 'detect_hardware':
const detection = await provider.detectHardware();
const summary = `
**Hardware Detection Results:**
- **Platform:** ${detection.platform}
- **Recommended Mode:** ${detection.recommended.toUpperCase()}
- **GPU Available:** ${detection.gpuAvailable ? 'Yes' : 'No'}
- **CUDA Supported:** ${detection.cudaSupported ? 'Yes' : 'No'}
- **Reason:** ${detection.reason}
- **Warnings:**\n${detection.warnings.map(w => ` - ${w}`).join('\n')}
`;
return {
content: [{ type: 'text', text: summary }]
};
case 'calibrate_prover_hardware':
return await provider.calibrateProver(args);
case 'create_prover':
return await provider.createProver(args);
case 'stake_to_prover':
return await provider.stakeToProver(args);
case 'get_prover_status':
const status = await provider.getProverNodeStatus(args);
const statusText = `**Status**: ${status.status}\n**Mode**: ${status.mode}\n**Started**: ${status.startTime}`;
return {
content: [{
type: 'text',
text: statusText,
}]
};
case 'stop_prover':
const result = await provider.stopProverNode(args);
return {
content: [{ type: 'text', text: result.message }]
};
case 'show_live_prover_logs':
const liveLogsResult = await provider.showLiveProverLogs(args);
if (!liveLogsResult.success) {
return {
content: [{ type: 'text', text: liveLogsResult.message || 'Failed to get live logs' }]
};
}
let liveText = `π΄ **Live Prover Logs**\n\n`;
liveText += `**Container Status**: ${liveLogsResult.status}\n`;
liveText += `**Current Activity**: ${liveLogsResult.activity}\n`;
liveText += `**Log Lines**: ${liveLogsResult.logs.length}\n\n`;
if (liveLogsResult.logs.length > 0) {
liveText += `**π Real-Time Activity:**\n`;
liveText += `\`\`\`\n${liveLogsResult.logs.join('\n')}\n\`\`\`\n\n`;
// Add activity interpretation
if (liveLogsResult.activity.includes('scanning')) {
liveText += `π‘ **Status**: Your prover is healthy and actively monitoring the network for new proof requests.\n`;
} else if (liveLogsResult.activity.includes('bidding')) {
liveText += `π― **Status**: Your prover found requests and is bidding - potential earnings incoming!\n`;
} else if (liveLogsResult.activity.includes('proving')) {
liveText += `β‘ **Status**: Your prover is working! Generating proofs and earning PROVE tokens.\n`;
} else if (liveLogsResult.activity.includes('fulfilled')) {
liveText += `π **Status**: Proof completed successfully! PROVE tokens earned.\n`;
}
liveText += `\n**π Refresh**: Use this command again to see updated logs.\n`;
liveText += `**βΉοΈ Stop**: Use "stop prover" to stop the prover.\n`;
} else {
liveText += `β οΈ No logs available yet. The prover might be starting up.\n`;
}
return {
content: [{ type: 'text', text: liveText }]
};
case 'stream_prover_logs_to_terminal':
// Get docker logs command for terminal execution
const streamResult = await provider.executeDockerLogsInTerminal(args);
if (!streamResult.success) {
return {
content: [{ type: 'text', text: streamResult.message }]
};
}
// Provide clear instructions for terminal execution
let response = streamResult.message;
if (streamResult.command) {
response += `\n**π― Execute this command in your terminal:**\n`;
response += `\`\`\`bash\n${streamResult.command}\n\`\`\`\n\n`;
response += `**π Steps:**\n`;
response += `1. Copy the command above\n`;
response += `2. Paste into your terminal (same as npm run build)\n`;
response += `3. Press Enter to start live streaming\n`;
response += `4. Press Ctrl+C to stop\n\n`;
response += `**π‘ Alternative MCP Tools (no terminal needed):**\n`;
response += `β’ "Show live prover logs" - Smart formatted logs\n`;
response += `β’ "Auto stream prover logs" - Automated refresh\n`;
}
return {
content: [{ type: 'text', text: response }]
};
case 'auto_stream_prover_logs':
const autoStreamResult = await provider.autoStreamProverLogs(args);
return {
content: [{ type: 'text', text: autoStreamResult.message }]
};
default:
throw new Error(`Unknown prover tool: ${name}`);
}
}