Historic mining rewards are returned as part of STX balance, they shouldn't be.
Describe the bug I believe there is a bug in the balances API. This address has about a 3 STX balance, but the API returns a balance of 81k STX, the balance returned by the API is equal to the (historic) mining rewards +the actual balance.
https://api.hiro.so/extended/v2/addresses/SP12QK2PQZXTWPF3K9ES0MA51JW17KXMBYG3W2V9M/balances/stx {"balance":"81284617344","total_miner_rewards_received":"81281147927","lock_tx_id":"","locked":"0","lock_height":0,"burnchain_lock_height":0,"burnchain_unlock_height":0}
To Reproduce Steps to reproduce the behavior:
- Go to https://api.hiro.so/extended/v2/addresses/SP12QK2PQZXTWPF3K9ES0MA51JW17KXMBYG3W2V9M/balances/stx
- See this in the API result
"balance":"81284617344" - Compare with actual balance on https://explorer.hiro.so/address/SP12QK2PQZXTWPF3K9ES0MA51JW17KXMBYG3W2V9M?chain=mainnet
- See error, the balances do not match
Expected behavior I expect the API to return the same balance as displayed on the explorer. It appears that the historic mining rewards are included in the balance even when they are not present in the account.
Screenshots
Complete API result
{"balance":"81284617344","total_miner_rewards_received":"81281147927","lock_tx_id":"","locked":"0","lock_height":0,"burnchain_lock_height":0,"burnchain_unlock_height":0}
Another miner with 0.000~ STX balance
https://api.hiro.so/extended/v2/addresses/SP3MCVE3HJP6T8QS4A9R0ETVJVGRZJA04MKJTPHG5/balances/stx
{"balance":"2373249411583","total_miner_rewards_received":"2373249409914","lock_tx_id":"","locked":"0","lock_height":0,"burnchain_lock_height":0,"burnchain_unlock_height":0}
Console log n.a.
Desktop (please complete the following information): n.a.
Smartphone (please complete the following information): n.a.
I did some preliminary investigation to help the team. Please provide feedback on how I can improve this format in the future
Overview
GET /extended/v2/addresses/:principal/balances/stx sometimes returns an STX balance that is higher than the real on-chain amount. The number includes each historical miner reward twice, so any account that has ever mined shows an inflated balance.
Root Cause Analysis
The balance handler already pulls the current balance from ft_balances, which is continuously updated by the ingestion path (see updateMinerRewards in src/datastore/pg-write-store.ts).
Right after that, the handler queries getStxMinerRewardsAtBlock for the cumulative miner rewards, then adds that total to the balance:
// src/api/routes/v2/addresses.ts (approx line 145)
const { totalMinerRewardsReceived } =
await fastify.db.v2.getStxMinerRewardsAtBlock({ sql, stxAddress, blockHeight: chainTip.block_height });
stxBalance += totalMinerRewardsReceived; // <-- duplicates rewards
``` :contentReference[oaicite:0]{index=0}
Because ft_balances already contains every matured miner reward, the extra addition overstates the true balance by the sum of all rewards.
Proposed fix
- Delete the line that mutates
stxBalancewith+= totalMinerRewardsReceived.- File:
src/api/routes/v2/addresses.ts - Block: inside the
/:principal/balances/stxhandler, right after the miner-reward query.
- File:
After the change the endpoint will:
- Return
balanceexactly as recorded inft_balances. - Still publish
total_miner_rewards_receivedas a separate field for clients that need historical reward totals.
No other schema changes required.
Proposed Test Coverage
1. Baseline sanity
-
Setup
- Build simple transfer-only blocks with
TestBlockBuilder.
- Build simple transfer-only blocks with
-
Steps
- Query
GET /extended/v1/address/:addr/stxGET /extended/v2/addresses/:addr/balances/stx
- Compare the returned balances.
- Query
-
Expectation
- Both endpoints return identical balances, confirming no behavioral regressions.
2. Mined-and-spent flow
-
Setup
- Block 1: credit a matured miner reward to
addrAviaaddMinerReward. - Block 2: spend the entire reward from
addrAtoaddrB(fee 0). - Apply both blocks with
db.update.
- Block 1: credit a matured miner reward to
-
Steps
- Query the same two endpoints for
addrA.
- Query the same two endpoints for
-
Expectation
balance→"0"on both v1 and v2 responses.total_miner_rewards_receivedequals the mined amount.
3. Pending outbound transaction
-
Setup
- From the state above, craft a mempool transaction sending 1 STX from
addrA.
- From the state above, craft a mempool transaction sending 1 STX from
-
Steps
- Query
GET /extended/v2/addresses/:addrA/balances/stx?include_mempool=true
- Query
-
Expectation
balanceremains"0".estimated_balancereflects the pending debit ("-1"STX).
Other considerations
- Backwards compatibility — Are there any downstream services that have silently compensated for this that will be in error when this is fixed?
- Docs — The OpenAPI spec already documents
total_miner_rewards_received; they do not appear to need an update.
:tada: This issue has been resolved in version 8.11.2 :tada:
The release is available on:
Your semantic-release bot :package::rocket:
Thanks for the report @314159265359879 this should be fixed via https://github.com/hirosystems/stacks-blockchain-api/pull/2302