Skip to content

Tell solo miners the truth about how they get paid - #42

Merged
rsantacroce merged 1 commit into
2026-08-22-pool-identity-bannerfrom
2026-08-22-about-numbers-by-mode
Aug 22, 2026
Merged

Tell solo miners the truth about how they get paid#42
rsantacroce merged 1 commit into
2026-08-22-pool-identity-bannerfrom
2026-08-22-about-numbers-by-mode

Conversation

@rsantacroce

Copy link
Copy Markdown
Collaborator

Stacked on #41 — it reuses the res.locals.pool wiring from that PR. Base is
2026-08-22-pool-identity-banner; retarget to main once #41 merges.

The problem

The "about the numbers" card on / stated the pps-classic story
unconditionally. On a solo pool every substantive claim in it was wrong:

Claim Reality in solo
"This is the PPS-classic build" There is no such build. pool_mode decides.
"credits your worker … 1 000 sats × share difficulty" Nothing accrues. A non-block share is worth nothing.
"Accruals accumulate … in pps_credits" No pps_credits rows are ever written.
pool wallet → BIP300 deposit → payout worker None of it exists. The coinbase pays the finder.
username: <your-Thunder-address> Rejected.

That last row is the one that costs real time. src/stratum.c:849 branches on
pps_enabled: pps-classic requires a Thunder address, solo requires a Bitcoin
address. So the card told solo miners to do the one thing that cannot work,
then explained the opposite error message — they'd hit "invalid payout
address in stratum username"
while reading a paragraph about "invalid
thunder address"
.

And on both modes the rate was a lie of a different kind: 1 000 sats × share difficulty was hardcoded HTML. The real rate is derived per template as
(block value ÷ net difficulty) × (1 − fee) and moves with difficulty. That
literal had never been checked against the ledger it purported to describe.

What it says now

views/partial/about-numbers.ejs, three bodies, branching on pool_mode.

solo

This pool runs in solo mode. Each block it finds pays the miner who found it, directly in that block's coinbase. Nothing accrues between blocks: a share that does not become a block earns nothing — it only proves you are working and sets your difficulty. There is no PPS credit, no pool wallet and no Thunder payout in this mode.

When one of your shares does become a block, the coinbase pays your address the block reward minus the operator fee of 1.00%, which goes to tb1qw508…. Coinbase outputs mature after 100 confirmations (roughly 16 hours), so a block shows up here well before it is spendable in your wallet. If the fee would come to under 546 sats it is dropped as dust and you take the whole reward.

Connect a minerusername: <your-bitcoin-address>[.<rig_label>]

The username must be a Bitcoin address this pool can build an output for: P2WPKH (tb1q…), P2PKH (m… / n…) or P2SH (2…) on signet. Taproot (tb1p…) is not supported and is rejected at authorize, as is a Thunder address — that is the other mode. Both fail with "invalid payout address in stratum username".

Two things there were nowhere in the docs before: coinbase maturity, and
that taproot is rejected (coinbase_address_to_script handles v0 witness
programs, P2PKH and P2SH only). Both were previously discovered by hitting
them.

pps-classic

Keeps the deposit and payout-worker paragraphs — those were always correct —
and replaces the rate sentence:

The rate is not a fixed number. It is derived from each block template as (block value ÷ network difficulty) × (1 − fee), so it moves with difficulty and with the fees in the template. Right now: 2 783.22 sats per unit of share difficulty, net of the 1.00% operator fee (gross 2 811.33). Every credit is stored with the exact rate it was paid at, so you can recompute your own balance on your worker page rather than take the pool's word for it.

A pinned rate (rate_source = override) now gets called out with the fee it
actually implies and a link to /health — the exact failure the rate columns
were added to expose. Before the first template it says the rate hasn't been
published yet rather than rendering 0.00 sats.

unknown mode

Names both and commits to neither. The modes differ on what a share is worth
and on what the username must be, so a guess is not a harmless default.

Everything is live now

Rate, gross, fee, operator address, pool wallet, sidechain id (THUNDER_SIDECHAIN_ID,
not a hardcoded #9), stratum URL — all from pool_meta via the
res.locals.pool from #41. Address examples follow the pool's network, so a
signet pool shows tb1q…; an ambiguous network (test/signet/regtest, which
is what a base58 operator address yields) falls back to prose rather than
inventing a prefix.

Testing

10 new tests in dashboard/test/about-numbers.test.js, 94 total in the suite
(92 pass / 2 pre-existing /proc skips). The ones worth naming:

  • solo mentions Thunder exactly twice, both negations — pinned by count, so copy drift cannot quietly reintroduce the pps story
  • solo never emits your-Thunder-address; pps-classic never emits your-bitcoin-address
  • the 1 000 sats literal is asserted absent
  • address examples match the network, and a signet pool does not show bc1q…
  • an end-to-end index.ejs render against real stats.* output, so the partial can't break the page it lives on

Also rendered by hand against a live dashboard in both modes; the transcripts
are what's quoted above.

Note

The solo and pps cards both name addresses that PR #41's identity strip also
shows a few lines up. Kept deliberately — the strip is reference, the card is
explanation, and "the fee goes here" reads better inline. Easy to drop if
you'd rather not have the repetition.

The "about the numbers" card stated the pps-classic story unconditionally.
On a solo pool every substantive claim in it was wrong, and one of them was
wrong in a way that costs a miner an afternoon: it told them to authorize
with a Thunder address. stratum.c branches on pps_enabled at authorize, so
in solo mode that is rejected — "invalid payout address in stratum username"
— and the card then went on to explain the opposite error message.

The card is now a partial with three bodies, branching on pool_mode.

Solo says what solo does: the coinbase pays the finder directly, nothing
accrues between blocks, a share that is not a block is worth nothing. It
names the operator fee and where it goes, and adds two things nobody had
written down — coinbase outputs mature after 100 confirmations, and the
address must be P2WPKH, P2PKH or P2SH, because coinbase_address_to_script
rejects taproot. Both are questions the old card left a solo miner to
discover by hitting them.

PPS-classic keeps the deposit and payout-worker explanation, which was
always correct, and replaces the rate sentence. "Currently 1 000 sats ×
share difficulty" was hardcoded HTML describing a rate that is derived per
template as (block value / net difficulty) * (1 - fee) and moves with
difficulty; the literal had never been checked against the ledger it
purported to describe. It now reads the live rate, the gross, and the fee
from pool_meta, and says outright that the rate is not fixed. A pinned rate
gets called out with the fee it actually implies, which is the failure the
rate columns exist to expose.

Everything else that was a literal is now live too: fee, operator address,
pool wallet, sidechain id. Address examples follow the pool's network, so a
signet pool shows tb1q rather than bc1q — misleading a miner about the
network is the same class of error this whole card was making.

An unknown mode names both and commits to neither. The two differ on what a
share is worth and on what the username must be, so a guess there is not a
harmless default.
@rsantacroce
rsantacroce merged commit 5a36812 into 2026-08-22-pool-identity-banner Aug 22, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant