Skip to content
Technical documentation

How NexusPool works, end to end

One page for the whole design: where the pool runs, what a rig speaks to it, how a solo block pays, what the signed receipts prove, how new work reaches you after a block, and how to check any of it yourself. Every number here was measured on the boxes that serve you, on the date given beside it.

Last reviewed 2026-09-13 · Bitcoin Core 31.0 on every region

01System architecture

NexusPool is four independent pools that share one published address. Each region runs its own Bitcoin Core node, its own stratum core and its own control database, and signs with the same authority key. A rig that reaches any of them gets the same terms, the same key and the same receipts.

Regions and the steered name

regionrolewhat it holds
Los Angelesprimary, and the origin of the authority keyfull pool: node, core, control, this website
Chicagofull pool, North America eastnode, core, control
Frankfurtfull pool, Europenode, core, control
Singaporefull pool, Asia and Oceanianode, core, control

solo.nexuspool.io is a GeoDNS name: the answer depends on where the query comes from, and a region only appears in the answer while the balancer's health monitor sees it serving work. Measured on 2026-09-12 with client-subnet queries against the authoritative server: US west resolves to Los Angeles, US east to Chicago, Germany to Frankfurt, Singapore and Australia to Singapore.

Failover is DNS plus a reconnect. When a region stops serving, it leaves the answer, and the next lookup lands on a healthy one. DNS cannot move a socket that is already open, so a rig keeps its current connection until that socket closes, then reconnects to whatever the name resolves to. Since every region signs with one key, the reconnect is transparent to a rig that pinned it. The bare name nexuspool.io resolves to Los Angeles only and has no such failover; the steered name is the one to configure.

One design, two lineages

The canonical tree serves Bitcoin, Bitcoin Cash and Litecoin (with Dogecoin merged in). The Bitcoin regions run a narrower build of that tree: Bitcoin only, without the Scrypt and CashAddr paths, so the code on the box that pays you is the code that was tested for that chain and nothing else. Bitcoin Cash and Litecoin run on their own boxes with their own endpoints, published on their pages.

Bitcoin Core

settingvaluewhy
version31.0same on every region
dbcache1024 MBsized to fit the box. A cache larger than free memory swaps, and a swapping node syncs six times slower, measured on the Singapore node during its initial download in 2026-09.
prune10000 MBthe pool needs the tip and recent history, not an archive
maxconnections40enough peers to hear a block early, few enough to keep the node's own work cheap
txindex0not needed for mining. Measured: the index adds delay to every block notification.
zmqpubhashblocktcp://127.0.0.1:28332the block hash, published once the block is connected
zmqpubsequencetcp://127.0.0.1:28352the block-connect event, published earlier in the pipeline. See ZMQ and latency.

Layers

Four processes, each owning one thing. Nothing crosses a boundary except the interface listed for it.

  1. Bitcoin Core validates the chain. It hands the core a template over RPC and announces new blocks over ZMQ.
  2. The core (C) speaks stratum, builds jobs, grades shares, submits blocks and seals the Glass Ledger. It writes events to a local socket and to its own append-only journal.
  3. Control (TypeScript, SQLite) consumes those events into a database and serves the read-only HTTP API on the loopback interface.
  4. The website (Next.js, server-rendered) proxies GET /api/* to control, adds caching, and pushes live snapshots to open pages over a socket. It never writes to the pool.

The order matters for trust. A miner's work is counted in the core and signed there; control and the website only read what the core wrote. The receipts described below come from the core's journal, not from the database that renders the charts.

02Mining protocols

One port serves both protocols. The core reads the first byte of a new connection: a JSON brace is Stratum V1, anything else is a Stratum V2 handshake. There is no second address to find and no second port to open.

Endpoints

protocoladdressnotes
Stratum V1stratum+tcp://solo.nexuspool.io:3350plain TCP, JSON-RPC lines
Stratum V2stratum2+tcp://solo.nexuspool.io:3350/9amd6GUzTaGXASESCa75c9Rx3vWYihRyLUAE3Vrmqwgm3T9jtxNNoise-encrypted; the base58 path segment is the authority key to pin
TLSnot servednothing terminates TLS behind the balancer, so no stratum+ssl:// address is published. Measured 2026-09-13: port 3443 is closed on both names. Stratum V2 is the encrypted option.

Stratum V1, as implemented

messagedirectionwhat NexusPool does with it
mining.subscriberig to poolanswers with the session's extranonce1 and the extranonce2 size
mining.authorizerig to poolusername is <btc-address>.<worker>; the address is the payout and the identity. Password x, or d=NNNN to pin difficulty.
mining.set_difficultypool to rigthe share target; changes as VarDiff settles
mining.notifypool to riga job. clean_jobs=true means the previous job is stale and must be dropped.
mining.submitrig to poolgraded against the job's target; accepted, or rejected with the reason (stale, low difficulty, duplicate, bad ntime)

After a new block the pool sends two jobs, not one. The first is a skeleton: a coinbase-only job built from the block header alone, armed 0.47 ms after the tip on Los Angeles (measured 2026-09-10) and sent before the node has even finished updating its own tip. It is valid work on the right parent, with no fees. The second is the full template with transactions, sent as soon as it exists, with clean_jobs=true. The skeleton stops your rig from hashing a dead block during the gap. Section 06 covers what fills that gap.

Stratum V2, as implemented

The handshake is a Noise protocol exchange. The pool proves it holds the private half of the authority key, and everything after the handshake is encrypted and authenticated. A rig that pins the key refuses any pool presenting a different one, which is what makes the steered name safe for V2: all four regions hold the same key.

featurestatus
standard channelsserved; the rig receives ready-made headers and searches the nonce
extended channelsserved; the rig receives the coinbase prefix and suffix and rolls its own extranonce
job declarationimplemented and enabled on Los Angeles since 2026-09-11. A rig may declare its own transaction set; the pool checks that the declared coinbase pays the channel's own identity before accepting it.
template distributionnot implemented. The pool is not a template provider for other pools.

V1 or V2, for the rig

  • Encryption. V1 is plain text on the wire. V2 is encrypted after the handshake.
  • Identity of the pool. V1 trusts whatever answers the address. V2 pins a key; a pool without it is refused.
  • Work format. V1 sends the full merkle branch per job. V2 standard channels send a header to search, which is smaller and cheaper for the firmware.
  • Who picks transactions. V1: the pool. V2 with job declaration: the rig may.
  • What you type. V1 takes a host and port. V2 takes the single URL above, or the key in hex or base58 in a separate field, depending on the firmware.

03Solo mining model

Every share you submit is graded against the current network target as well as your share target. A share that clears the network target is a block. The pool submits it, and the coinbase of that block pays your address. There is no pool balance, no payout queue and no fee: the coinbase carries one output for the reward, to you.

The odds

P(block) = h · t / (D · 2³²)h = your hashrate in hashes per second · t = seconds mined · D = network difficulty. Each hash is one draw at probability 1 / (D · 2³²); the pool does not change it.

The site states this wherever odds appear, and never claims to improve them. What the pool can affect is waste: hashes spent on a stale parent, or work that a pool counted wrong. The rest of this page is about those two things.

VarDiff

Difficulty starts at 512 for a new connection and moves so that your rig submits shares at a steady rate. It exists because a share target too low floods the pool with shares that carry no information, and one too high leaves the dashboard blind for minutes at a time. It has no effect on your odds: a block is a block at any share difficulty.

The tuner is a state machine. A rig that reports its own hashrate is anchored to that figure and stops searching; a rig that reports nothing is measured from its shares and settles within a few windows; an anchor that goes stale is released so the rig is not held at a difficulty nobody vouches for anymore. Two figures matter to a reader of the stats:work is the difficulty the pool assigned, sdiff is the best difficulty a share actually achieved. Set the password to d=NNNN to pin a value and turn the tuner off for that connection.

Stale shares

A stale share is work on a block that is no longer the tip. It happens in the window between a new block appearing on the network and your rig receiving the next job; every hash in that window is wasted. NexusPool keeps that window short in three ways, each measured: it hears about the block from the sequence topic rather than waiting for the hash (section 07), it sends the skeleton job in under a millisecond, and it fans that job out to every connection at 14.5 µs per connection at the median (Los Angeles, 2026-09-10). The pool-wide stale rate is published live on /api/pool as stale_rate.

04Glass Ledger and custody

The Glass Ledger is a signed log. The core writes a record for every share window it counted per address, every tip change, every block it won, and seals each with a BIP-340 Schnorr signature under the region's authority key. The key is public, the byte layout is public, the records are served over HTTP. Anyone can check them.

The receipt

fieldmeaning
seqposition in the journal, monotonic per region. A gap is visible.
typecustody.hash (work counted for one address), custody.time (a tip arriving and how long fresh work took), custody.block (a won block), custody.region (which region sealed the following records)
payloadfor custody.hash: address, worker, shares counted, best share, the window, and attest: counted (from shares the pool received) or reconciled (also checked against the rig's own telemetry, when the firmware reports it)
sig64 bytes, Schnorr over the digest below

The signed bytes are the domain NexusPool/Custody/v1, then seq as a little-endian uint64, the type with its length, and the payload with its length. The digest is one SHA-256 of that; the signature verifies against the x-only key with any Taproot-capable library. The exact procedure, with a thirty-line verifier, is /docs/audit; this page does not repeat it.

Endpoints

pathserves
/.well-known/nexuspool-custody.jsonthe algorithm, the preimage layout, and one authority key per chain. Retired keys stay listed so older receipts keep verifying.
/api/custody-receipts?addr=…the recent receipts for one address, plus the pool-wide records. addr=sample returns a page to test a verifier against.

The keys served right now:

chainauthority public key (x-only, hex)
btc124797e8bc0ec89ed60b77c30e1770c624407f72659dc97aa4c4fd7d9377106c
bch39e0a44418a2f0d831fc9166ffd4f1373bb6f263d753ed6d08862da8ed4a1d46
ltc450a714033c4b01731820bca8c32f00f3eb72c56c14b3c8b43cb0a7d0f357387

Verifier status

The browser verifier on Glass Ledger shows the preimage byte by byte and lets you flip one to watch the signature fail. A standalone offline verifier exists as a statically linked binary and has checked every receipt on every region; its public release is still pending, so the published path today is the Python snippet on /docs/audit, which needs nothing from NexusPool to run.

Trust model

the pool signsyou verifywhat nobody can deny
the shares it counted per window, the difficulty it served, your best share, and each tip and block it sawthat every record was sealed by the holder of the published key, that not one byte changed since, and that the sequence has no holesa receipt the pool later contradicts. Once signed and served, the count cannot be revised without the revision being provable.

What a receipt does not prove. That the pool counted every share you sent: a share dropped before counting leaves no record. That your hashrate was what the firmware reports. A signature makes the pool accountable for what it wrote, not for silence. Read the honest-limits section on Glass Ledger before relying on a receipt for more than that.

05Block Pulse

Block Pulse measures one thing per block: how long after a new block reaches an external vantage the pool has full work on the wire. It is an instrument for the pool's own speed, not a comparison with anyone else.

The vantage is a machine that is not the pool. It runs npnotify.mjs, which connects to the stratum port like a rig, records every job it receives with a timestamp, and reads the merkle branch length to tell a full template from a skeleton. It also hears blocks from the network on its own. The difference between the two timestamps is one sample; control keeps a ring of the most recent 200 in its key-value store and /api/block-pulse serves them.

Median lead on propagation is the median of those samples when it is negative: the pool's job arrived at the vantage before the vantage heard of the block from the network. A positive median is labelled median time to work. The sign convention is deliberate: a negative number printed as a time reads as a broken panel, so the page names the case instead. Measured on mainnet, the lead has reached −98 ms.

Limits. One vantage is one place on the network; a different vantage reads different numbers, and both are right. The vantage's clock is the vantage's. A merkle branch proves a floor on the transaction count, never the exact count. This is a laboratory measurement of the pool's reflex, not a field measurement of your rig. Read it on Block Pulse.

06T' (template subtraction)

T' is the job between the skeleton and the full template. When a block arrives, the pool still holds the previous template, and most of its transactions are still valid on the new tip. Instead of waiting for Bitcoin Core to assemble a new template, T' subtracts from the one it has: it removes every transaction the new block confirmed and every transaction that conflicts with one it confirmed, recomputes fees, weight, the witness commitment and the coinbase, and publishes. Measured over 32 blocks on 2026-09-12, the body is ready 26 ms after the tip on Los Angeles and 21 ms on Frankfurt.

The three broadcasts per tip

jobwhencarries
skeleton0.95 ms after the tip (median, Los Angeles, 2026-09)coinbase only, no fees
T'32.4 msthe subtracted template: most of the fees, verified by the gates below
full template71.2 msBitcoin Core's own getblocktemplate

The gates

A T' that fails any gate is not published; the rig keeps the skeleton until the full template lands. In the order the code checks them:

gatewhat it refuses
NAK latcheverything, for the rest of the process, if the node ever refused a published T'. Checked first and never cleared while running.
G7subtracting from anything but a template the node itself built and validated. Never from a previous T': the validity argument holds per hop, not across hops.
G1a block body that did not fully decode. A partial parse would leave transactions the block already invalidated.
timinga body that arrived so late the full template is effectively here. Past that point T' buys nothing.
G2, G8a block whose parent is not the parent our template was built on, compared as 32 raw bytes, and a body that is not the block we asked for. This is the 2-for-1 reorg case: a competing chain wins with two blocks, the node jumps two heights at once, the new tip sits at the height we targeted and a height check passes while every held transaction was validated on coins that may not exist. The raw prevhash comparison refuses it.
G4, G5a target height on a retarget boundary or a halving. Only getblocktemplate knows the bits across a retarget, and an off-by-one at a halving costs the whole reward. One block in 2016 is the cheap side of both.
G6a subsidy figure the node's own numbers disagree with. A zero counts as disagreement, never as a pass.

Ratchet and canary

The full template always supersedes T'. T' never supersedes a newer template, and a T' whose parent has already been passed is dropped. Separately, a canary thread submits each T' body to Bitcoin Core in proposal mode, which validates without broadcasting. A rejection is logged and sets the latch above. The canary never gates a real submit: a block that is valid does not become invalid because a check was slow.

07ZMQ and latency

Bitcoin Core publishes a new block on several ZMQ topics, and they do not fire at the same time. sequence announces the block-connect event earlier in the node's pipeline than hashblock does. Measured across 343 blocks with all topics subscribed on one machine: sequence led hashblock on 343 of 343, by 20.17 ms at the median. The pool subscribes to both and acts on whichever arrives first.

The price of sequence is noise: the same topic carries every mempool add and remove. Measured at 2,017 messages per minute, of which 99.97% are mempool traffic and discarded by type before any work is done. A dedup ring keeps a burst of two blocks from being processed twice, and a divergence check has to persist for five seconds before the pool treats the subscription as wedged and rebuilds it.

The path from tip to rig

  1. A block-connect event arrives on sequence, or the node's log wakes an inotify watcher, whichever is first.
  2. The skeleton is armed from the header: 0.47 ms on Los Angeles, before the node's own tip has updated.
  3. The job is written to every open connection. The loop costs 14.5 µs per connection at the median and 23.6 µs at the 90th percentile, serial, so the last rig on a fully loaded box waits about 10 ms behind the first. Today it is a fraction of that.
  4. T' and then the full template follow on the schedule in section 06.

Two things that were measured and deliberately not done: a second node per region (no expected value at the pool's scale) and a peer-to-peer tap that would read compact blocks off the wire before the node validates them. The first job is already out before the node's tip updates; what remains is the node's validation time, and bypassing validation is not a trade a solo pool should make.

08Security

One authority key

124797e8bc0ec89ed60b77c30e1770c624407f72659dc97aa4c4fd7d9377106cx-only public key, hex. Base58 form for SV2 clients: 9amd6GUzTaGXASESCa75c9Rx3vWYihRyLUAE3Vrmqwgm3T9jtxN

All four regions load this key, verified from each core's own log on 2026-09-12. A rig that pins it in firmware refuses any pool presenting another, and the steered name can hand it to any region without the pin breaking. Keys that a region used before the consolidation stay published in the custody document so receipts sealed under them still verify.

Threat model

threatposition
block withholdinga rig that finds a block and does not submit it hurts only itself: the coinbase pays the finder, so there is no pool pot to sabotage. As a signal it is statistically undetectable at the pool's current scale, and the pool does not claim to detect it.
2-for-1 reorgthe case where a height check passes and the parent is wrong. Refused by gate G2 in T'; the skeleton and the full template are built from the node's own tip and are not exposed.
a fake pool at the addresson V1, undetectable by the protocol. On V2, refused by the key pin.
abuse of the stratum portper-IP connection caps, per-connection share caps, and reject-class accounting that separates stale from invalid, so a flood cannot be mistaken for a bad job.

Non-custodial

The pool never holds a key of yours and never holds a balance. The payout is the coinbase output of the block you found, to the address in your username, built into the block before it is submitted. Payout preflight rebuilds that exact coinbase for any address before a block exists, so you can see the output you would be paid.

09Public API

Read-only, JSON, no authentication for the public routes. The website proxies GET /api/* to control and sets the cache headers; anything else is not proxied. Rate limiting is per IP.

routereturnscache
/api/poolpool snapshot: workers online, hashrate windows, share counters, reject classes, stale rate, height, whether the node has a template3 s
/api/globeminer and peer positions by region, fuzzed upstream to the resolution of a city and never an IP3 s
/api/block-pulsethe vantage name, the ring cap and the samples3 s
/api/custody-receipts?addr=…signed receipts for one address plus the pool-wide records; capped, most recent first5 s
/api/hashrate, /api/blocks, /api/top/*history series, found blocks, leaderboards3 s
/api/user/…, /api/worker/…per-address and per-worker stats; the address is the scope and the only handle5 s

Profiles. Control runs as a public profile: every route above is readable without a key. Ingest routes (telemetry pushed by a rig or a bridge) require a key in the x-api-key header and are not reachable through the website proxy. The operations panel reads control over a private network with its own read key and is not part of the public surface.

10Operational guides

A Bitaxe, or any AxeOS device

  1. Open the device's web interface and go to its pool settings.
  2. Stratum host: solo.nexuspool.io. Port: 3350.
  3. User: your Bitcoin address, then a dot, then a worker name, for example bc1q….bitaxe1.
  4. Password: x. Save and restart the device.
  5. Within a minute, paste the address into the lookup on the home page to see the worker appear.

An Antminer S23 Hyd

Stratum V1, stock firmware.

  1. Miner configuration, pool 1: URL stratum+tcp://solo.nexuspool.io:3350.
  2. Worker: <bitcoin-address>.<name>. Password: x.
  3. Leave pools 2 and 3 empty or point them at the same address; the steered name already fails over.

Stratum V2. Use it if your firmware offers a Stratum V2 pool field, or through a V1-to-V2 translator running on your own network in front of the rig.

  1. URL: stratum2+tcp://solo.nexuspool.io:3350/9amd6GUzTaGXASESCa75c9Rx3vWYihRyLUAE3Vrmqwgm3T9jtxN.
  2. If the firmware asks for the key separately, paste the base58 key from section 08.
  3. The worker name and address go in the same place as for V1.

MiningRigRentals

  1. Add a pool in your MRR account: host solo.nexuspool.io, port 3350.
  2. Username: your Bitcoin address with a worker suffix.
  3. Password: d=NNNN, with NNNN a difficulty suited to the rented hashrate. Rented rigs arrive at full speed with no history, and a pinned difficulty avoids the tuner's first windows at a target that is far too low for them.
  4. Check the rental on the home page lookup once it starts; the address is the only identity the pool knows.

Verifying a receipt

  1. Fetch https://nexuspool.io/.well-known/nexuspool-custody.json and read the key for btc.
  2. Fetch https://nexuspool.io/api/custody-receipts?addr=<your-address>.
  3. For one receipt, rebuild the preimage in the field order the document gives, hash it once with SHA-256, and verify the 64-byte signature against the key with a Schnorr library.
  4. Change one character of the payload and verify again. It must fail. The full procedure and a ready verifier are on /docs/audit.

Reading Block Pulse

  • median lead on propagation, −N ms: the pool's full work reached the vantage N ms before the vantage heard of the block. Good.
  • median time to work, +N ms: the vantage heard of the block first, and full work followed N ms later.
  • depth: the merkle branch length of the job. A depth of L means at least 2 to the power of L minus 1, plus one, transactions were in the work. A depth of 0 is a skeleton.
  • The panel is one vantage. Your own rig, elsewhere, is a different measurement, and the latency check is how you take it.

Where this fits

The verification procedure lives on /docs/audit, the receipt scheme and its honest limits on Glass Ledger, the live propagation instrument on Block Pulse, and the capability-by-capability test record on Technology. This page links to them and does not repeat them.

Technical documentation: how NexusPool works, end to end · NexusPool