Skip to content

Instantly share code, notes, and snippets.

@Timonchegs
Last active December 11, 2025 03:11
Show Gist options
  • Select an option

  • Save Timonchegs/9c7cf0afc5b0979838c2f88ab33d9cfe to your computer and use it in GitHub Desktop.

Select an option

Save Timonchegs/9c7cf0afc5b0979838c2f88ab33d9cfe to your computer and use it in GitHub Desktop.
deepseek
// Abelcoin Stratum Proxy FINAL - Full AbelianStratum Support
const net = require('net');
const http = require('http');
const axios = require('axios');
const crypto = require('crypto');
const config = {
abelRpcHost: '127.0.0.1',
abelRpcPort: 8668,
stratumPort: 3333,
webPort: 8080,
rpcUser: 'tteFTlJ7YOfGDA2KBMHKqnDnXeE=',
rpcPass: 'SOkvF8sxay8ViOxpgbraHmqJmSU='
};
const miners = new Map();
let currentWork = null;
let sessionCounter = 0;
async function getAbelianWork() {
const auth = Buffer.from(`${config.rpcUser}:${config.rpcPass}`).toString('base64');
try {
const response = await axios.post(
`http://${config.abelRpcHost}:${config.abelRpcPort}`,
{
jsonrpc: '2.0',
method: 'getwork',
params: [],
id: 1
},
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Basic ${auth}`
},
timeout: 5000
}
);
if (response.data?.result) {
const work = response.data.result;
return {
jobId: work.jobid || 'default',
contentHash: work.contenthash?.startsWith('0x') ? work.contenthash.slice(2) : work.contenthash || crypto.randomBytes(32).toString('hex'),
epochSeed: work.epochseed?.startsWith('0x') ? work.epochseed.slice(2) : work.epochseed || crypto.randomBytes(32).toString('hex'),
target: work.targetboundary || '0000000000014b7c000000000000000000000000000000000000000000000000',
height: work.height || 1,
epoch: work.epoch || 100,
extranonce: work.extranonce || 0,
extranoncebitsnum: work.extranoncebitsnum || 16
};
}
} catch (error) {
console.error('RPC Error:', error.message);
}
return {
jobId: 'fallback_' + Date.now(),
contentHash: crypto.randomBytes(32).toString('hex'),
epochSeed: crypto.randomBytes(32).toString('hex'),
target: '0000000000014b7c000000000000000000000000000000000000000000000000',
height: 1,
epoch: 100,
extranonce: 0,
extranoncebitsnum: 16
};
}
class AbelianMinerConnection {
constructor(socket) {
this.socket = socket;
this.id = crypto.randomBytes(4).toString('hex');
this.sessionId = 'session_' + (++sessionCounter);
this.workerName = null;
this.authorized = false;
this.subscribed = false;
this.firstMiningSet = false;
console.log(`[${this.id}] New Abelian miner from ${socket.remoteAddress}`);
this.socket.on('data', (data) => this.handleData(data));
this.socket.on('error', (err) => console.error(`[${this.id}] Error:`, err.message));
this.socket.on('close', () => {
console.log(`[${this.id}] Disconnected`);
miners.delete(this.id);
});
}
handleData(data) {
try {
const messages = data.toString().trim().split('\n');
for (const msg of messages) {
if (msg) {
const json = JSON.parse(msg);
this.processMessage(json);
}
}
} catch (err) {
console.error(`[${this.id}] Parse error:`, err.message);
}
}
processMessage(msg) {
console.log(`[${this.id}] Received:`, JSON.stringify(msg));
switch (msg.method) {
case 'mining.hello':
this.handleHello(msg);
break;
case 'mining.subscribe':
this.handleSubscribe(msg);
break;
case 'mining.authorize':
this.handleAuthorize(msg);
break;
case 'mining.submit':
this.handleSubmit(msg);
break;
default:
console.log(`[${this.id}] Unknown method: ${msg.method}`);
}
}
handleHello(msg) {
console.log(`[${this.id}] Abelian hello received`);
this.send({
id: msg.id,
result: {
proto: 1,
encoding: "utf-8",
extranonce: "0000",
extranonce_size: 2,
version: "1.0.0",
motd: "Abelian Solo Proxy",
algo: "abelian",
protocol: "AbelianStratum",
resume: "1",
timeout: "300",
maxerrors: "3",
node: "1"
},
error: null
});
}
handleSubscribe(msg) {
console.log(`[${this.id}] Subscribing Abelian miner`);
this.subscribed = true;
this.send({
id: msg.id,
result: {
session_id: this.sessionId,
extra_nonce1: "0000",
extra_nonce2_size: 2
},
error: null
});
// Сохраняем майнера
miners.set(this.id, {
ip: this.socket.remoteAddress,
sessionId: this.sessionId,
connected: new Date(),
shares: 0
});
}
async handleAuthorize(msg) {
const workerName = msg.params?.[0] || 'unknown';
this.workerName = workerName;
this.authorized = true;
console.log(`[${this.id}] Authorized: ${workerName}`);
this.send({
id: msg.id,
result: {
worker: this.id,
registered: "0",
username: workerName
},
error: null
});
// После авторизации отправляем mining.set
await this.sendMiningSet();
}
async sendMiningSet() {
try {
const work = await getAbelianWork();
currentWork = work;
// Abelian mining.set message
this.send({
method: "mining.set",
params: {
epoch: work.epoch.toString(16),
target: work.target.slice(2, 58), // Без 0x и обрезанный
algo: "abelethash",
extranonce: work.extranonce.toString(16).padStart(4, '0'),
extra_nonce_bits_num: work.extranoncebitsnum.toString(16)
},
id: null
});
this.firstMiningSet = true;
console.log(`[${this.id}] Sent mining.set for epoch ${work.epoch}`);
// Сразу отправляем работу
await this.sendMiningNotify();
} catch (err) {
console.error(`[${this.id}] Failed to send mining.set:`, err.message);
}
}
async sendMiningNotify() {
if (!this.firstMiningSet) {
console.log(`[${this.id}] Cannot send notify before mining.set`);
return;
}
try {
const work = currentWork || await getAbelianWork();
// Abelian mining.notify (всего 4 параметра в объекте!)
this.send({
method: "mining.notify",
params: {
job_id: work.jobId.substring(0, 8),
height: work.height.toString(16),
content_hash: work.contentHash,
clean_job: "0"
},
id: null
});
console.log(`[${this.id}] Sent mining.notify job: ${work.jobId.substring(0, 8)}`);
} catch (err) {
console.error(`[${this.id}] Failed to send mining.notify:`, err.message);
}
}
async handleSubmit(msg) {
if (!this.authorized) {
this.send({ id: msg.id, result: false, error: "Not authorized" });
return;
}
console.log(`[${this.id}] Share submitted`);
// Обновляем статистику
const minerData = miners.get(this.id);
if (minerData) {
minerData.shares = (minerData.shares || 0) + 1;
minerData.lastShare = new Date();
}
// Подтверждаем шар (в реальности нужно проверять решение)
this.send({
id: msg.id,
result: true,
error: null
});
// Отправляем новую работу
await this.sendMiningNotify();
}
send(data) {
try {
const json = JSON.stringify(data) + '\n';
this.socket.write(json);
console.log(`[${this.id}] Sent:`, JSON.stringify(data));
} catch (err) {
console.error(`[${this.id}] Send failed:`, err.message);
}
}
}
function startWebServer() {
const server = http.createServer((req, res) => {
if (req.url === '/stats') {
const stats = {
totalMiners: miners.size,
miners: Array.from(miners.entries()).map(([id, data]) => ({
id,
...data
})),
currentWork: currentWork ? {
jobId: currentWork.jobId,
height: currentWork.height
} : null
};
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(stats, null, 2));
} else {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<html><head><title>Abelian Proxy</title>
<style>body { font-family: monospace; margin: 20px; }
.miner { border: 1px solid #ccc; padding: 10px; margin: 5px; }</style>
</head><body>
<h1>Abelian Stratum Proxy</h1>
<p>Miners: ${miners.size}</p>
<div id="stats"></div>
<script>
async function loadStats() {
const res = await fetch('/stats');
const data = await res.json();
document.getElementById('stats').innerHTML =
data.miners.map(m => \`
<div class="miner">
<strong>\${m.sessionId}</strong><br>
IP: \${m.ip}<br>
Shares: \${m.shares || 0}<br>
Connected: \${new Date(m.connected).toLocaleString()}
</div>
\`).join('');
}
setInterval(loadStats, 3000);
loadStats();
</script></body></html>
`);
}
});
server.listen(config.webPort, () => {
console.log(`Web interface: http://localhost:${config.webPort}`);
});
}
async function startProxy() {
console.log('='.repeat(60));
console.log('ABELIAN STRATUM PROXY - FINAL VERSION');
console.log('Protocol: Native AbelianStratum (not Bitcoin-compatible)');
console.log(`Port: ${config.stratumPort}`);
console.log('='.repeat(60));
// Проверяем ноду
try {
const test = await getAbelianWork();
console.log('✓ Node connected');
console.log(` Height: ${test.height}, Epoch: ${test.epoch}`);
} catch (err) {
console.error('✗ Node connection failed');
process.exit(1);
}
startWebServer();
const server = net.createServer((socket) => {
new AbelianMinerConnection(socket);
});
server.listen(config.stratumPort, '0.0.0.0', () => {
console.log(`✓ Listening on port ${config.stratumPort}`);
console.log(`✓ Connect miners to: stratum+tcp://77.82.85.68:${config.stratumPort}`);
console.log('='.repeat(60));
});
process.on('SIGINT', () => {
console.log('\nShutting down...');
server.close();
process.exit(0);
});
}
startProxy().catch(console.error);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment