Skip to content

Commit 58bdb31

Browse files
committed
Public per-worker page shows payout txids + blocks found
Miners can now audit their own Thunder txids without needing admin credentials. The stratum-username-is-your-Thunder-address model already exposes worker identity publicly, so adding payment history and blocks doesn't leak new sensitive info — it just closes the 'trust the operator' gap. Two new cards on /worker/:name below the existing audit card: 'Payouts to your Thunder address' — one row per settled Thunder tx from the pool to this address, with full txid, amount, fee, and 'when'. Empty-state copy explains why (reserve not funded yet). Footer shows running total paid + tx count. 'Blocks you found' — one row per block where this worker's share crossed the network target. Includes reward + fees + full block hash. Note explains how to look up the coinbase via 'bitcoin-cli getblock <hash> 2'. Only rendered when there's at least one block. stats.worker() now returns .payouts and .blocks alongside the existing .shares/.buckets/.pps_audit; the '{...w}' spread in the route auto-propagates them. Both queries are LIMITed (100 payouts, 25 blocks) so a mega-miner with tons of blocks doesn't blow up the page. Empty-state / missing-table handling: payouts SELECT is wrapped in try/catch so an ancient DB without the 'payouts' table still renders the page (dashboards deployed before the last migration). No fetch changes on the server side.
1 parent 676aa0a commit 58bdb31

2 files changed

Lines changed: 109 additions & 0 deletions

File tree

dashboard/lib/stats.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,35 @@ export function worker(handle, name, windowSec = 86400) {
272272
};
273273
}
274274

275+
/* Payouts to this worker — same table the admin dashboard reads;
276+
* miners can verify their own txids from the public view. */
277+
let payouts = [];
278+
try {
279+
payouts = d.prepare(`
280+
SELECT id, sats, fee_sats, txid, paid_at, note
281+
FROM payouts
282+
WHERE worker_id = ?
283+
ORDER BY paid_at DESC, id DESC
284+
LIMIT 100
285+
`).all(w.id).map(r => ({
286+
id: r.id, sats: Number(r.sats), fee_sats: Number(r.fee_sats),
287+
txid: r.txid, paid_at: Number(r.paid_at), note: r.note,
288+
}));
289+
} catch { /* payouts table missing on very old DBs — fine */ }
290+
291+
/* Blocks found BY this worker specifically. */
292+
const workerBlocks = d.prepare(`
293+
SELECT id, ts, height, hash, reward_sats, fee_sats
294+
FROM blocks_found
295+
WHERE finder_id = ?
296+
ORDER BY ts DESC, id DESC
297+
LIMIT 25
298+
`).all(w.id).map(r => ({
299+
id: r.id, ts: Number(r.ts), height: Number(r.height), hash: r.hash,
300+
reward_sats: Number(r.reward_sats || 0),
301+
fee_sats: Number(r.fee_sats || 0),
302+
}));
303+
275304
return {
276305
worker: {
277306
name: w.name,
@@ -285,6 +314,8 @@ export function worker(handle, name, windowSec = 86400) {
285314
buckets,
286315
window_sec: windowSec,
287316
pps_audit: ppsAudit,
317+
payouts,
318+
blocks: workerBlocks,
288319
};
289320
}
290321

dashboard/views/worker.ejs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,84 @@ if (n > 1) {
123123
without exposing other miners' data on this public page.
124124
</p>
125125
</section>
126+
127+
<section class="card">
128+
<h2>Payouts to your Thunder address</h2>
129+
<% if (!payouts || payouts.length === 0) { %>
130+
<p class="muted">
131+
No payouts recorded yet. Once the operator's Thunder
132+
reserve has enough BTC and the payout worker fires,
133+
each Thunder tx paying you will appear here with its
134+
txid so you can verify it in your own Thunder wallet.
135+
</p>
136+
<% } else { %>
137+
<p class="muted small" style="margin-top:0">
138+
Each row is one Thunder transaction the pool sent to your
139+
address. Verify by looking the txid up in your own Thunder
140+
wallet — the same tx should show as an incoming credit.
141+
</p>
142+
<table class="lb">
143+
<thead><tr>
144+
<th>When</th>
145+
<th>Amount</th>
146+
<th>Fee</th>
147+
<th>Thunder txid <span class="muted small">(click to copy)</span></th>
148+
</tr></thead>
149+
<tbody>
150+
<% payouts.forEach(p => { %>
151+
<tr>
152+
<td title="<%= fmtTs(p.paid_at) %>"><%= ago(p.paid_at) %></td>
153+
<td><strong><%= fmtSats(p.sats) %></strong></td>
154+
<td class="muted small"><%= fmtSats(p.fee_sats) %></td>
155+
<td class="mono small" style="word-break:break-all"><%= p.txid %></td>
156+
</tr>
157+
<% }) %>
158+
</tbody>
159+
</table>
160+
<p class="muted small" style="margin-top:0.75em">
161+
Total paid to date:
162+
<strong><%= fmtSats(payouts.reduce((a, p) => a + p.sats, 0)) %></strong>
163+
across <%= payouts.length %> transaction<%= payouts.length === 1 ? '' : 's' %>.
164+
</p>
165+
<% } %>
166+
</section>
167+
168+
<% if (blocks && blocks.length > 0) { %>
169+
<section class="card">
170+
<h2>Blocks you found</h2>
171+
<p class="muted small" style="margin-top:0">
172+
Blocks where <em>your</em> share crossed the network target.
173+
Even though PPS pays you the same per share regardless of
174+
whether a share becomes a block, blocks are what actually
175+
fund the pool BTC wallet that later gets deposited to
176+
Thunder to pay you. Look up the coinbase of each block
177+
with <code>bitcoin-cli getblock &lt;hash&gt; 2</code>.
178+
</p>
179+
<table class="lb">
180+
<thead><tr>
181+
<th>When</th>
182+
<th>Height</th>
183+
<th>Reward</th>
184+
<th>Fees</th>
185+
<th>Block hash</th>
186+
</tr></thead>
187+
<tbody>
188+
<% blocks.forEach(b => { %>
189+
<tr>
190+
<td title="<%= fmtTs(b.ts) %>"><%= ago(b.ts) %></td>
191+
<td><%= fmtN(b.height) %></td>
192+
<td><%= fmtSats(b.reward_sats) %></td>
193+
<td class="muted small"><%= fmtSats(b.fee_sats) %></td>
194+
<td class="mono small" style="word-break:break-all"><%= b.hash %></td>
195+
</tr>
196+
<% }) %>
197+
</tbody>
198+
</table>
199+
<p class="muted small" style="margin-top:0.75em">
200+
<%= blocks.length %> block<%= blocks.length === 1 ? '' : 's' %> found · pool BTC earned:
201+
<strong><%= fmtSats(blocks.reduce((a, b) => a + b.reward_sats + b.fee_sats, 0)) %></strong>
202+
</p>
203+
</section>
126204
<% } %>
127205
128206
<section class="card">

0 commit comments

Comments
 (0)