Dashboard: - Add aggregate stats across all running miners (total hashrate, shares) - Add workers table with per-miner stats, efficiency, and controls - Show hashrate bars and efficiency badges for each worker - Support stopping individual workers or all at once TT-Miner: - Implement Install, Start, GetStats, CheckInstallation, Uninstall - Add TT-Miner to Manager's StartMiner and ListAvailableMiners - Support GPU-specific config options (devices, intensity, cliArgs) Chart: - Improve styling with WA-Pro theme variables - Add hashrate unit formatting (H/s, kH/s, MH/s) - Better tooltip and axis styling Also: - Fix XMRig download URLs (linux-static-x64, windows-x64) - Add Playwright E2E testing infrastructure - Add XMR pool research documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
19 KiB
19 KiB
Pool Integration Guide for Mining UI
Quick Start: Adding Pool Support to Your Miner
This guide provides code examples for integrating XMR pool data into your mining application.
Part 1: TypeScript/JavaScript Implementation
Loading Pool Database
import poolDatabase from './xmr-pools-database.json';
interface MiningPool {
id: string;
name: string;
website: string;
fee_percent: number;
minimum_payout_xmr: number;
stratum_servers: StratumServer[];
authentication: AuthConfig;
}
interface StratumServer {
region_id: string;
region_name: string;
hostname: string;
ports: PoolPort[];
}
interface PoolPort {
port: number;
difficulty: string;
protocol: "stratum+tcp" | "stratum+ssl";
description: string;
}
interface AuthConfig {
username_format: string;
password_default: string;
registration_required: boolean;
}
interface ConnectionConfig {
url: string;
username: string;
password: string;
pool_name: string;
pool_fee: number;
}
// Load pools
const pools: MiningPool[] = poolDatabase.pools;
// Find a specific pool
function getPool(poolId: string): MiningPool | undefined {
return pools.find(p => p.id === poolId);
}
// Get all pools sorted by recommendation
function getRecommendedPools(userType: 'beginner' | 'advanced' | 'solo'): MiningPool[] {
const recommendedIds = poolDatabase.recommended_pools[userType + 's'];
return recommendedIds.map(id => getPool(id)).filter(p => p !== undefined);
}
Connection String Generator
class PoolConnector {
/**
* Generate complete connection configuration for a mining pool
*/
static generateConnectionConfig(
poolId: string,
walletAddress: string,
workerName: string = "default",
preferTls: boolean = false,
difficulty: 'standard' | 'medium' | 'high' = 'standard'
): ConnectionConfig {
const pool = getPool(poolId);
if (!pool) throw new Error(`Pool ${poolId} not found`);
// Select primary stratum server (usually first region)
const stratumServer = pool.stratum_servers[0];
// Find port matching difficulty preference
let selectedPort = stratumServer.ports[0]; // Default to first (usually standard)
if (difficulty === 'medium' && stratumServer.ports.length > 1) {
selectedPort = stratumServer.ports.find(p => p.difficulty === 'medium') || stratumServer.ports[0];
} else if (difficulty === 'high' && stratumServer.ports.length > 2) {
selectedPort = stratumServer.ports.find(p => p.difficulty === 'high') || stratumServer.ports[0];
}
// Use TLS if preferred and available
if (preferTls) {
const tlsPort = stratumServer.ports.find(p => p.protocol === 'stratum+ssl');
if (tlsPort) selectedPort = tlsPort;
}
// Build connection URL
const url = `${selectedPort.protocol}://${stratumServer.hostname}:${selectedPort.port}`;
// Build username (most pools use wallet.worker format)
const username = `${walletAddress}.${workerName}`;
return {
url,
username,
password: pool.authentication.password_default,
pool_name: pool.name,
pool_fee: pool.fee_percent
};
}
/**
* Test connection to a pool
*/
static async testConnection(config: ConnectionConfig, timeoutMs: number = 5000): Promise<boolean> {
try {
const urlObj = new URL(config.url);
const hostname = urlObj.hostname;
const port = parseInt(urlObj.port);
return new Promise((resolve) => {
const socket = new net.Socket();
const timeout = setTimeout(() => {
socket.destroy();
resolve(false);
}, timeoutMs);
socket.connect(port, hostname, () => {
clearTimeout(timeout);
socket.destroy();
resolve(true);
});
socket.on('error', () => {
clearTimeout(timeout);
resolve(false);
});
});
} catch (error) {
return false;
}
}
/**
* Get fallback pool if primary is unavailable
*/
static async findWorkingPool(
poolIds: string[],
walletAddress: string
): Promise<ConnectionConfig | null> {
for (const poolId of poolIds) {
const config = this.generateConnectionConfig(poolId, walletAddress);
if (await this.testConnection(config)) {
return config;
}
}
return null;
}
}
// Usage examples:
const config = PoolConnector.generateConnectionConfig(
'supportxmr',
'4ABC1234567890ABCDEF...',
'miner1',
false,
'standard'
);
console.log(`Pool URL: ${config.url}`);
console.log(`Username: ${config.username}`);
console.log(`Password: ${config.password}`);
// Test connection
const isConnected = await PoolConnector.testConnection(config);
console.log(`Pool online: ${isConnected}`);
// Find working pool from list
const workingConfig = await PoolConnector.findWorkingPool(
['supportxmr', 'nanopool', 'moneroocean'],
walletAddress
);
React Component: Pool Selector
// PoolSelector.tsx
import React, { useState, useEffect } from 'react';
import poolDatabase from './xmr-pools-database.json';
interface PoolSelectorProps {
onPoolSelect: (config: ConnectionConfig) => void;
walletAddress: string;
userType?: 'beginner' | 'advanced' | 'solo';
}
export const PoolSelector: React.FC<PoolSelectorProps> = ({
onPoolSelect,
walletAddress,
userType = 'beginner'
}) => {
const [selectedPoolId, setSelectedPoolId] = useState('supportxmr');
const [selectedDifficulty, setSelectedDifficulty] = useState('standard');
const [useTls, setUseTls] = useState(false);
const [connectionConfig, setConnectionConfig] = useState<ConnectionConfig | null>(null);
const recommendedPools = poolDatabase.recommended_pools[userType + 's'];
const availablePools = poolDatabase.pools.filter(p =>
recommendedPools.includes(p.id)
);
useEffect(() => {
const config = PoolConnector.generateConnectionConfig(
selectedPoolId,
walletAddress,
'default',
useTls,
selectedDifficulty as any
);
setConnectionConfig(config);
}, [selectedPoolId, useTls, selectedDifficulty]);
const handleConnect = () => {
if (connectionConfig) {
onPoolSelect(connectionConfig);
}
};
return (
<div className="pool-selector">
<h2>Mining Pool Configuration</h2>
<div className="form-group">
<label>Select Pool:</label>
<select value={selectedPoolId} onChange={(e) => setSelectedPoolId(e.target.value)}>
{availablePools.map(pool => (
<option key={pool.id} value={pool.id}>
{pool.name} - {pool.fee_percent}% fee (Min payout: {pool.minimum_payout_xmr} XMR)
</option>
))}
</select>
</div>
<div className="form-group">
<label>Difficulty Level:</label>
<select value={selectedDifficulty} onChange={(e) => setSelectedDifficulty(e.target.value)}>
<option value="standard">Standard (Auto-adjust)</option>
<option value="medium">Medium</option>
<option value="high">High (Powerful miners)</option>
</select>
</div>
<div className="form-group">
<label>
<input
type="checkbox"
checked={useTls}
onChange={(e) => setUseTls(e.target.checked)}
/>
Use TLS/SSL Encryption
</label>
</div>
{connectionConfig && (
<div className="connection-preview">
<h3>Connection Details:</h3>
<code>
<div>URL: {connectionConfig.url}</div>
<div>Username: {connectionConfig.username}</div>
<div>Password: {connectionConfig.password}</div>
</code>
<button onClick={handleConnect} className="btn-primary">
Connect to {connectionConfig.pool_name}
</button>
</div>
)}
</div>
);
};
Part 2: Go Implementation
Go Structs and Functions
package mining
import (
"encoding/json"
"fmt"
"net"
"time"
)
type PoolPort struct {
Port int `json:"port"`
Difficulty string `json:"difficulty"`
Protocol string `json:"protocol"`
Description string `json:"description"`
}
type StratumServer struct {
RegionID string `json:"region_id"`
RegionName string `json:"region_name"`
Hostname string `json:"hostname"`
Ports []PoolPort `json:"ports"`
}
type AuthConfig struct {
UsernameFormat string `json:"username_format"`
PasswordDefault string `json:"password_default"`
RegistrationRequired bool `json:"registration_required"`
}
type MiningPool struct {
ID string `json:"id"`
Name string `json:"name"`
Website string `json:"website"`
Description string `json:"description"`
FeePercent float64 `json:"fee_percent"`
MinimumPayoutXMR float64 `json:"minimum_payout_xmr"`
StratumServers []StratumServer `json:"stratum_servers"`
Authentication AuthConfig `json:"authentication"`
LastVerified string `json:"last_verified"`
ReliabilityScore float64 `json:"reliability_score"`
Recommended bool `json:"recommended"`
}
type ConnectionConfig struct {
URL string
Username string
Password string
PoolName string
PoolFee float64
}
type PoolDatabase struct {
Pools []MiningPool `json:"pools"`
RecommendedPools map[string][]string `json:"recommended_pools"`
}
// LoadPoolDatabase loads pools from JSON file
func LoadPoolDatabase(filePath string) (*PoolDatabase, error) {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}
var db PoolDatabase
if err := json.Unmarshal(data, &db); err != nil {
return nil, err
}
return &db, nil
}
// GetPool retrieves a pool by ID
func (db *PoolDatabase) GetPool(poolID string) *MiningPool {
for i := range db.Pools {
if db.Pools[i].ID == poolID {
return &db.Pools[i]
}
}
return nil
}
// GenerateConnectionConfig creates a connection configuration
func GenerateConnectionConfig(
pool *MiningPool,
walletAddress string,
workerName string,
useTLS bool,
difficulty string,
) *ConnectionConfig {
if pool == nil || len(pool.StratumServers) == 0 {
return nil
}
server := pool.StratumServers[0]
if len(server.Ports) == 0 {
return nil
}
// Select port based on difficulty
selectedPort := server.Ports[0]
for _, port := range server.Ports {
if port.Difficulty == difficulty {
if !useTLS && port.Protocol == "stratum+tcp" {
selectedPort = port
break
} else if useTLS && port.Protocol == "stratum+ssl" {
selectedPort = port
break
}
}
}
// If TLS requested but not found, look for TLS port
if useTLS {
for _, port := range server.Ports {
if port.Protocol == "stratum+ssl" {
selectedPort = port
break
}
}
}
url := fmt.Sprintf("%s://%s:%d",
selectedPort.Protocol,
server.Hostname,
selectedPort.Port,
)
username := fmt.Sprintf("%s.%s", walletAddress, workerName)
return &ConnectionConfig{
URL: url,
Username: username,
Password: pool.Authentication.PasswordDefault,
PoolName: pool.Name,
PoolFee: pool.FeePercent,
}
}
// TestConnection tests if a pool is reachable
func TestConnection(hostname string, port int, timeoutSecs int) bool {
address := fmt.Sprintf("%s:%d", hostname, port)
conn, err := net.DialTimeout("tcp", address, time.Duration(timeoutSecs)*time.Second)
if err != nil {
return false
}
defer conn.Close()
return true
}
// FindWorkingPool attempts to connect to multiple pools and returns first working one
func (db *PoolDatabase) FindWorkingPool(
poolIDs []string,
walletAddress string,
timeoutSecs int,
) *ConnectionConfig {
for _, poolID := range poolIDs {
pool := db.GetPool(poolID)
if pool == nil {
continue
}
if len(pool.StratumServers) == 0 || len(pool.StratumServers[0].Ports) == 0 {
continue
}
server := pool.StratumServers[0]
port := server.Ports[0]
if TestConnection(server.Hostname, port.Port, timeoutSecs) {
return GenerateConnectionConfig(pool, walletAddress, "default", false, "standard")
}
}
return nil
}
// GetRecommendedPools returns pools recommended for user type
func (db *PoolDatabase) GetRecommendedPools(userType string) []*MiningPool {
poolIDs := db.RecommendedPools[userType+"s"]
var pools []*MiningPool
for _, id := range poolIDs {
if pool := db.GetPool(id); pool != nil {
pools = append(pools, pool)
}
}
return pools
}
Go Usage Examples
package main
import (
"fmt"
"log"
)
func main() {
// Load pool database
db, err := LoadPoolDatabase("xmr-pools-database.json")
if err != nil {
log.Fatal("Failed to load pool database:", err)
}
walletAddress := "4ABC1234567890ABCDEF..."
// Example 1: Get recommended pools for beginners
recommendedPools := db.GetRecommendedPools("beginner")
fmt.Println("Recommended pools for beginners:")
for _, pool := range recommendedPools {
fmt.Printf(" - %s (%.1f%% fee)\n", pool.Name, pool.FeePercent)
}
// Example 2: Generate connection config
pool := db.GetPool("supportxmr")
config := GenerateConnectionConfig(pool, walletAddress, "miner1", false, "standard")
fmt.Printf("\nConnection Config:\n")
fmt.Printf(" URL: %s\n", config.URL)
fmt.Printf(" Username: %s\n", config.Username)
fmt.Printf(" Password: %s\n", config.Password)
// Example 3: Test connection
isOnline := TestConnection("pool.supportxmr.com", 3333, 5)
fmt.Printf("Pool online: %v\n", isOnline)
// Example 4: Find first working pool
poolIDs := []string{"supportxmr", "nanopool", "moneroocean"}
workingConfig := db.FindWorkingPool(poolIDs, walletAddress, 5)
if workingConfig != nil {
fmt.Printf("\nWorking pool found: %s\n", workingConfig.PoolName)
fmt.Printf("URL: %s\n", workingConfig.URL)
}
}
Part 3: Configuration Storage
Saving User Pool Selection
// Save to localStorage
function savePoolPreference(poolId: string, walletAddress: string) {
localStorage.setItem('preferred_pool', poolId);
localStorage.setItem('wallet_address', walletAddress);
}
// Load from localStorage
function loadPoolPreference(): { poolId: string; walletAddress: string } | null {
const poolId = localStorage.getItem('preferred_pool');
const walletAddress = localStorage.getItem('wallet_address');
if (poolId && walletAddress) {
return { poolId, walletAddress };
}
return null;
}
Persisting to Config File (Go)
type UserConfig struct {
PreferredPoolID string `json:"preferred_pool_id"`
WalletAddress string `json:"wallet_address"`
WorkerName string `json:"worker_name"`
UseTLS bool `json:"use_tls"`
Difficulty string `json:"difficulty"`
LastUpdated string `json:"last_updated"`
}
func SaveUserConfig(filePath string, config *UserConfig) error {
config.LastUpdated = time.Now().Format(time.RFC3339)
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(filePath, data, 0644)
}
func LoadUserConfig(filePath string) (*UserConfig, error) {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}
var config UserConfig
if err := json.Unmarshal(data, &config); err != nil {
return nil, err
}
return &config, nil
}
Part 4: UI Components
Pool List Display
// Display pool information with comparison
function PoolComparison() {
const pools = poolDatabase.pools;
return (
<table className="pool-comparison">
<thead>
<tr>
<th>Pool Name</th>
<th>Fee</th>
<th>Min Payout</th>
<th>Reliability</th>
<th>Recommended</th>
</tr>
</thead>
<tbody>
{pools.map(pool => (
<tr key={pool.id}>
<td>
<a href={pool.website} target="_blank">
{pool.name}
</a>
</td>
<td>{pool.fee_percent}%</td>
<td>{pool.minimum_payout_xmr} XMR</td>
<td>
<ProgressBar value={pool.reliability_score * 100} max={100} />
</td>
<td>{pool.recommended ? '✓' : '-'}</td>
</tr>
))}
</tbody>
</table>
);
}
Connection String Copy-to-Clipboard
function ConnectionDisplay({ config }: { config: ConnectionConfig }) {
const [copied, setCopied] = useState(false);
const connectionString = `${config.url}\n${config.username}\n${config.password}`;
const handleCopy = () => {
navigator.clipboard.writeText(connectionString);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="connection-display">
<pre>{connectionString}</pre>
<button onClick={handleCopy}>
{copied ? 'Copied!' : 'Copy to Clipboard'}
</button>
</div>
);
}
Part 5: Validation & Error Handling
Wallet Address Validation
// XMR address validation
function validateXMRAddress(address: string): boolean {
// Standard Monero address
// - 95 characters long
// - Starts with 4 (mainnet) or 8 (stagenet/testnet)
// - Base58 characters only
const base58Regex = /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/;
return (
(address.startsWith('4') || address.startsWith('8')) &&
address.length === 95 &&
base58Regex.test(address)
);
}
function validatePoolConfiguration(config: ConnectionConfig): ValidationResult {
const errors: string[] = [];
if (!config.url) errors.push('Pool URL required');
if (!config.username) errors.push('Username required');
if (!config.url.includes('://')) errors.push('Invalid protocol format');
return {
isValid: errors.length === 0,
errors
};
}
Part 6: Migration Guide
If you have existing hardcoded pool configs:
// OLD CODE (hardcoded):
const poolConfig = {
url: 'stratum+tcp://pool.supportxmr.com:3333',
username: 'wallet.worker',
password: 'x'
};
// NEW CODE (from database):
const poolId = 'supportxmr';
const pool = poolDatabase.pools.find(p => p.id === poolId);
const config = PoolConnector.generateConnectionConfig(
poolId,
'wallet_address',
'worker',
false,
'standard'
);
Summary
Your mining UI can now:
- Load pools from the JSON database
- Display pool selection interface
- Generate connection strings dynamically
- Validate pool connectivity
- Save user preferences
- Suggest fallback pools
- Support both TCP and TLS connections
- Auto-detect optimal difficulty levels
This approach makes it easy to:
- Update pools without code changes
- Add new pools instantly
- Validate connection details
- Scale to other cryptocurrencies