Files
salvium-rs/bin/miner.js
T
Matt Hess 7730b6993f ● Add AssemblyScript WASM VM for RandomX full mode
- Create assembly/vm.ts with full RandomX VM implementation
    - 256 instructions, 2048 iterations per hash
    - Native u64/f64 operations in WebAssembly
    - Full mode dataset lookups (2GB pre-computed)

  - Update miner to use WASM VM for full mode
    - mining-worker-asm.js uses pre-compiled WASM
    - ~32 H/s per thread (4x faster than light mode)
    - 8 threads achieves ~260 H/s

  - Clean up redundant code
    - Remove mining-worker-full.js (old JIT approach)
    - Consolidate 'asm' mode into 'full' mode
2026-01-18 01:16:10 +00:00

283 lines
9.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* Salvium JS Miner - CLI Tool
*
* Usage:
* bun run bin/miner.js --pool stratum+tcp://pool.example.com:3333 --wallet SAL... --worker myrig
*
* Options:
* --pool, -p Pool URL (stratum+tcp:// or stratum+ssl://)
* --wallet, -w Wallet address
* --worker, -n Worker/rig name (default: salvium-js)
* --password Pool password (default: x)
* --threads, -t Number of threads (default: CPU cores - 1)
* --mode, -m Mining mode: light (256MB) or full (2GB WASM VM)
* --help, -h Show help
*/
import { StratumMiner, getAvailableCores } from '../src/index.js';
import { RandomXFullMode, RANDOMX_DATASET_SIZE } from '../src/randomx/full-mode.js';
// ANSI colors
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
red: '\x1b[31m'
};
function printBanner() {
console.log(`
${colors.cyan}╔═════════════════════════════════════════════════════════════╗
║ ║
${colors.bright}███████╗ █████╗ ██╗ ██╗ ██╗██╗██╗ ██╗███╗ ███╗${colors.reset}${colors.cyan}
${colors.bright}██╔════╝██╔══██╗██║ ██║ ██║██║██║ ██║████╗ ████║${colors.reset}${colors.cyan}
${colors.bright}███████╗███████║██║ ██║ ██║██║██║ ██║██╔████╔██║${colors.reset}${colors.cyan}
${colors.bright}╚════██║██╔══██║██║ ╚██╗ ██╔╝██║██║ ██║██║╚██╔╝██║${colors.reset}${colors.cyan}
${colors.bright}███████║██║ ██║███████╗╚████╔╝ ██║╚██████╔╝██║ ╚═╝ ██║${colors.reset}${colors.cyan}
${colors.bright}╚══════╝╚═╝ ╚═╝╚══════╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝${colors.reset}${colors.cyan}
║ ║
${colors.yellow}JavaScript RandomX Miner${colors.cyan}
║ v1.0.1 ║
╚═════════════════════════════════════════════════════════════╝${colors.reset}
`);
}
function printHelp() {
console.log(`
${colors.bright}Usage:${colors.reset}
bun run bin/miner.js [options]
${colors.bright}Required:${colors.reset}
--pool, -p Pool URL (stratum+tcp://host:port or stratum+ssl://host:port)
--wallet, -w Your Salvium wallet address
${colors.bright}Optional:${colors.reset}
--worker, -n Worker/rig name (default: salvium-js)
--password Pool password (default: x)
--threads, -t Number of mining threads (default: ${getAvailableCores() - 1})
--mode, -m Mining mode: light or full (default: light)
--help, -h Show this help message
${colors.bright}Mining Modes:${colors.reset}
light Each thread uses 256MB RAM for RandomX cache
Typical hashrate: ~10-15 H/s per thread
full Pre-computes 2GB dataset (shared across all threads)
Uses AssemblyScript WASM VM for ~40-50 H/s per thread
Requires 2.3GB RAM, dataset generation takes ~30-60 seconds
${colors.bright}Examples:${colors.reset}
bun run bin/miner.js -p stratum+tcp://pool.salvium.io:3333 -w SAL1abc...xyz
bun run bin/miner.js -p stratum+ssl://pool.salvium.io:443 -w SAL1abc...xyz -t 4
bun run bin/miner.js -p stratum+tcp://pool.salvium.io:3333 -w SAL1abc...xyz -m full
`);
}
function parseArgs() {
const args = process.argv.slice(2);
const options = {
pool: null,
wallet: null,
worker: 'salvium-js',
password: 'x',
threads: Math.max(1, getAvailableCores() - 1),
mode: 'light' // 'light' or 'full'
};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
const next = args[i + 1];
switch (arg) {
case '--pool':
case '-p':
options.pool = next;
i++;
break;
case '--wallet':
case '-w':
options.wallet = next;
i++;
break;
case '--worker':
case '-n':
options.worker = next;
i++;
break;
case '--password':
options.password = next;
i++;
break;
case '--threads':
case '-t':
options.threads = parseInt(next) || options.threads;
i++;
break;
case '--mode':
case '-m':
if (next === 'light' || next === 'full') {
options.mode = next;
} else {
console.error(`${colors.red}Error: Invalid mode "${next}". Use "light" or "full"${colors.reset}`);
process.exit(1);
}
i++;
break;
case '--help':
case '-h':
printHelp();
process.exit(0);
break;
}
}
return options;
}
function formatUptime(ms) {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
} else if (minutes > 0) {
return `${minutes}m ${seconds % 60}s`;
} else {
return `${seconds}s`;
}
}
function formatHashrate(h) {
if (h >= 1000000) return `${(h / 1000000).toFixed(2)} MH/s`;
if (h >= 1000) return `${(h / 1000).toFixed(2)} KH/s`;
return `${h.toFixed(2)} H/s`;
}
async function main() {
printBanner();
const options = parseArgs();
// Validate required options
if (!options.pool) {
console.error(`${colors.red}Error: Pool URL is required${colors.reset}`);
console.log('Use --help for usage information');
process.exit(1);
}
if (!options.wallet) {
console.error(`${colors.red}Error: Wallet address is required${colors.reset}`);
console.log('Use --help for usage information');
process.exit(1);
}
// Format wallet: first 8 chars...last 8 chars
const walletDisplay = options.wallet.length > 20
? `${options.wallet.substring(0, 8)}...${options.wallet.slice(-8)}`
: options.wallet;
// Calculate memory usage
let memoryDisplay;
if (options.mode === 'full') {
const datasetGB = RANDOMX_DATASET_SIZE / (1024 * 1024 * 1024);
memoryDisplay = `${datasetGB.toFixed(2)}GB dataset + 256MB cache (shared)`;
} else {
memoryDisplay = `${options.threads * 256}MB (${options.threads} × 256MB cache)`;
}
console.log(`${colors.bright}Configuration:${colors.reset}`);
console.log(` Pool: ${colors.cyan}${options.pool}${colors.reset}`);
console.log(` Wallet: ${colors.cyan}${walletDisplay}${colors.reset}`);
console.log(` Worker: ${colors.cyan}${options.worker}${colors.reset}`);
console.log(` Threads: ${colors.cyan}${options.threads}${colors.reset}`);
console.log(` Mode: ${colors.cyan}${options.mode}${colors.reset}`);
console.log(` Memory: ${colors.cyan}${memoryDisplay}${colors.reset}`);
console.log();
// Create miner
const miner = new StratumMiner({
pool: options.pool,
wallet: options.wallet,
worker: options.worker,
password: options.password,
threads: options.threads,
mode: options.mode
});
// Event handlers
miner.on('datasetProgress', (info) => {
process.stdout.write(`\r${colors.yellow}Dataset generation: ${info.percent}%${colors.reset} `);
});
miner.on('datasetReady', (info) => {
console.log(`\n${colors.green}✓ Dataset ready${colors.reset} (${info.timeSeconds.toFixed(1)}s)`);
});
miner.on('started', (info) => {
console.log(`${colors.green}✓ Mining started${colors.reset} (${info.mode} mode)`);
});
miner.on('job', (job) => {
console.log(`${colors.blue}► New job${colors.reset} height=${job.height}`);
});
miner.on('share', (share) => {
console.log(`${colors.yellow}★ Share found${colors.reset} nonce=${share.nonce}`);
});
miner.on('accepted', (share) => {
console.log(`${colors.green}✓ Share accepted${colors.reset}`);
});
miner.on('rejected', (share) => {
console.log(`${colors.red}✗ Share rejected${colors.reset} - ${share.reason}`);
});
miner.on('hashrate', (info) => {
const stats = miner.getStats();
process.stdout.write(
`\r${colors.cyan}${formatHashrate(info.hashrate)}${colors.reset} | ` +
`Shares: ${colors.green}${stats.sharesAccepted}${colors.reset}/${colors.red}${stats.sharesRejected}${colors.reset} | ` +
`Uptime: ${formatUptime(stats.uptime)} `
);
});
miner.on('error', (err) => {
console.error(`${colors.red}Error: ${err.message}${colors.reset}`);
});
// Handle shutdown
process.on('SIGINT', () => {
console.log(`\n${colors.yellow}Shutting down...${colors.reset}`);
miner.stop();
const stats = miner.getStats();
console.log(`\n${colors.bright}Final Statistics:${colors.reset}`);
console.log(` Total hashes: ${stats.totalHashes.toLocaleString()}`);
console.log(` Shares accepted: ${stats.sharesAccepted}`);
console.log(` Shares rejected: ${stats.sharesRejected}`);
console.log(` Uptime: ${formatUptime(stats.uptime)}`);
process.exit(0);
});
// Start mining
try {
await miner.start();
} catch (err) {
console.error(`${colors.red}Failed to start miner: ${err.message}${colors.reset}`);
process.exit(1);
}
}
main().catch(err => {
console.error('Fatal error:', err);
process.exit(1);
});