Skip to content

feat: pox-5 reward claims, STX-staking rewards, and principal staking endpoints#2582

Merged
rafa-stacks merged 9 commits into
pox5from
pox5--claim-rewards
Jun 17, 2026
Merged

feat: pox-5 reward claims, STX-staking rewards, and principal staking endpoints#2582
rafa-stacks merged 9 commits into
pox5from
pox5--claim-rewards

Conversation

@rafa-stacks

@rafa-stacks rafa-stacks commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Description

As an application developer building pox-5 staking UIs, I want a principal's staking rewards — what they've accrued and claimed — and their staking positions available through the API, for bond (sBTC) staking and STX staking alike, without expensive on-read aggregation.

This PR ingests pox-5 reward claims and the STX-staking reward stream (on top of the bond reward accrual that already existed), materializes the per-principal staking totals, and restructures the principal staking endpoints.

Motivation. The @stacks/codec upgrade made reward claims fully attributable: claim-staker-rewards-for-signer identifies the staker (and bond, or null for STX staking), and claim-rewards carries the per-signer aggregate. STX-staking rewards (paid in sBTC by calculate-rewards) previously had no per-staker materialization. The original single staking endpoint also conflated a paginated list (bond positions, each with lifecycle status) with a singleton (STX staking) under a /balances/ path, and computed per-principal totals with SUM/COUNT on every read.

What changed

Reward ingestion

  1. Per-staker bond claimsclaim-staker-rewards-for-signer with a bond_index is recorded in principal_bond_reward_claims and rolled into principal_bond_positions.claimed_rewards.
  2. STX-staking reward accrual — each calculate-rewards event's STX-staker pool is split across current pox-5 STX lockers by locked weight (floor(locked_ustx × accrued_rewards_per_ustx / 1e18)) into principal_stx_reward_distributions source rows + the running per-principal total; claim-staker-rewards-for-signer events with a null bond_index feed the claimed side.
  3. Per-signer claim aggregateclaim-rewards is recorded in signer_reward_claims (STX + per-bond breakdown + totals) as audit/bookkeeping only.
  4. All running totals are maintained on write and corrected on reorg via flip-and-delta (mirroring ft_balances/ft_events); the audit aggregate only flips its canonical flag.

Materialized per-principal staking summary

  • New principal_staking_totals table — one row per principal holding the STX-staking reward totals (stx_accrued_rewards, stx_claimed_rewards) and a rollup of the principal's bond positions (bond_count, bond_btc_locked, bond_stx_locked, bond_accrued_rewards, bond_claimed_rewards). Maintained incrementally at the same delta sites that update the per-bond bonds aggregate, so the staking summary is a single-row lookup — no SUM/COUNT on read (the bond-positions list endpoint also sources its total from bond_count).
  • The pox-5 STX locked amount stays resolved-on-read from stx_locked_balances (expiry is burn-height-driven with no event at expiry), consistent with /balances/stx.

Endpoints — the old GET /extended/v3/principals/:principal/balances/staking (a bare array that mixed bond positions and STX staking) is replaced by two resources under /staking:

  • GET /extended/v3/principals/:principal/staking — one-call summary:
{
  "stx":   { "locked": "50000000", "rewards": { "btc": { "accrued": "8000", "claimed": "0", "claimable": "8000" } } },
  "bonds": { "count": 3, "locked": { "btc": "5000", "stx": "30000000" },
             "rewards": { "btc": { "accrued": "12000", "claimed": "4000", "claimable": "8000" } } }
}
  • GET /extended/v3/principals/:principal/staking/bonds — cursor-paginated bond positions (by bond_index), each flattened so status/enrollment are peers of the amounts:
{ "bond_index": 0, "status": "running", "active": true,
  "enrollment": { "tx_id": "0x…", "btc_lockup": { "amount": "1000" } },
  "locked": { "btc": "1000", "stx": "10000000" },
  "rewards": { "btc": { "accrued": "2000", "claimed": "1500", "claimable": "500" } } }

STX-staking rewards are paid in sBTC, hence rewards.btc in both shapes.

How this impacts application developers

A principal's staking is now two clean resources: a singleton summary and a paginated bond-positions list. Reward totals (accrued/claimed/claimable) are available per bond position, per STX-staking position, and as per-principal aggregates.

Type of Change

  • New feature

Does this introduce a breaking change?

Yes, on the unreleased pox-5 line (no released API surface affected):

  • GET /extended/v3/principals/:principal/balances/staking is removed, replaced by GET .../staking (summary) and GET .../staking/bonds (paginated positions).
  • The bond position shape is flattened: the previous balances.{locked,rewards} wrapper is gone — status, active, enrollment, locked, and rewards are now top-level, and the flat accrued_rewards is replaced by rewards.btc.{accrued,claimed,claimable}.

Are documentation updates required?

  • openapi.yaml is regenerated in this PR (the generated client schema is left to release automation). External docs may want the new shapes once pox-5 ships.

Testing information

  1. Included — simulated-ingestion tests in tests/api/pox5/bonds.test.ts (no live chain): bond reward accrual + claim, STX-staking accrual + claim + expiry, the signer-claim aggregate, the materialized summary aggregate (multi-position + distribute→claim→reorg), bond-positions cursor pagination, and reorg-revert for all of it. 39 pox5 API tests pass.
  2. N/A (feature, not a bug fix).
  3. Affected paths: pox-5 event ingestion and markEntitiesCanonical reorg blocks in pg-write-store.ts; the v3 staking read path; the staking schema/serializer/routes.
  4. Builds on the already-merged locked-STX table (feat: materialized locked-STX table and v3 principal balance endpoints #2577) and bond-registration lockup capture (feat: capture bond registration BTC/sBTC lockup data #2579).
  5. Watch out for: the per-staker reward split uses current locked weight at calculation time (same approximation already used for bond distributions); STX-staking claims share the principal_bond_reward_claims table with a null bond_index; principal_staking_totals is maintained by signed deltas, so its invariants depend on every position-mutating site applying the matching delta.

DB tables added/changed

  • principal_staking_totals (new) — materialized per-principal staking summary (STX reward totals + bond rollup).
  • principal_bond_reward_claims (new) — per-staker claim ledger (bond and, with null bond_index, STX-staking claims).
  • principal_stx_reward_distributions (new) — STX-staking accrual source rows.
  • signer_reward_claims (new) — per-signer claim aggregate (audit).
  • principal_bond_positions (changed) — added claimed_rewards column.

@codecov

codecov Bot commented Jun 16, 2026

Copy link
Copy Markdown

@rafa-stacks rafa-stacks changed the title feat: pox-5 reward claims and STX-staking reward accrual feat: pox-5 reward claims, STX-staking rewards, and principal staking endpoints Jun 17, 2026
@rafa-stacks rafa-stacks merged commit 35fabeb into pox5 Jun 17, 2026
37 of 40 checks passed
@rafa-stacks rafa-stacks deleted the pox5--claim-rewards branch June 17, 2026 16:53
@github-project-automation github-project-automation Bot moved this to ✅ Done in API Board Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

1 participant