Read-Only Queries
Informational reads — positions, bond browsing, ratio quotes, and schedule helpers. No transactions.
Read-only queries cover the surface a wallet, explorer, or dashboard hits to render PoX-5 state without sending a transaction. The flows below group them by what they answer:
Positions — what a given address has locked, across STX-only and paired-bond legs.
Bond browsing — capacity, target APY, ratio, and the caller's allowlist entry for an upcoming bond.
Ratio quotes — required STX for a given BTC commitment under a bond's static ratio.
Schedule helpers — cycle, burn-height, and bond-period conversions for "X days until next bond opens" and re-lock window timing.
Read a staker's current position
A wallet or dashboard fetches everything needed to render "what does this address have locked?" in one shot.
import {
fetchAccountStatus,
fetchBondMembership,
fetchPoxInfo,
fetchStakerInfo,
} from '@stacks/bitcoin-staking';
const network = 'mainnet';
const address = user.stxAddress;
const [pox, account, stxOnly, bond] = await Promise.all([
fetchPoxInfo({ network }),
fetchAccountStatus({ address, network }),
fetchStakerInfo({ address, network }), // STX-only position (if any)
fetchBondMembership({ address, network }), // paired-bond position (if any)
]);
const position = {
burnHeight: pox.currentBurnchainBlockHeight,
cycle: pox.rewardCycleId,
liquidUstx: account.balance - account.locked,
lockedUstx: account.locked,
unlockHeight: account.unlockHeight,
stxOnly: stxOnly.staked ? stxOnly.details : null,
bond: bond ?? null,
};fetchStakerInfo returns { staked: false } if the address has no STX-only lock, or { staked: true, details: { amountUstx, firstRewardCycle, numCycles, signer } } — details is the decoded staker-info tuple. The details.signer field (pox-5.clar:224) is the principal a client passes as oldSignerManager to stake-update / unstake.
fetchBondMembership returns the decoded protocol-bond-memberships tuple — { bondIndex, amountUstx, signer, isL1Lock, amountSats }. amountSats is the BTC shares currently attributed to the membership — an L1 lockup total when isL1Lock is true, an sBTC contribution when false — and is the authoritative source for per-cycle bond share accounting. fetchProtocolBondMemberships reads the raw protocol-bond-memberships map entry and does not filter out expired memberships, unlike fetchBondMembership (which goes through get-bond-membership) — the latter returns undefined once the bond's unlock cycle is reached.
Browse an upcoming bond
A partner or pool operator inspects the next bond's parameters before enrolling: capacity, target APY, ratio, open burn height, and their own allowlist entry.
fetchBond returns the decoded protocol-bonds tuple — { bondIndex, targetRateBps, stxValueRatio, minUstxRatioBps, earlyUnlockBytes }. The earlyUnlockBytes field is the early-exit subscript embedded in the L1 lockup script (a pre-pushed Bitcoin script fragment that validates the early-exit key(s) signature and must leave a valid boolean result on the stack — consumed by the shared OP_VERIFY after OP_ENDIF — e.g. <pubkey> OP_CHECKSIG or an M-of-N OP_CHECKMULTISIG template). In practice every deployed bond uses the single-key form; the M-of-N template is permitted by the contract but unused. The openBurnHeight and firstRewardCycle are not stored on the bond — derive them with the per-bond conversions (bondPeriodToBurnHeight, bondPeriodToRewardCycle), which take { bondIndex, poxInfo } and read the first bond-period cycle off poxInfo.contractVersions internally (firstPox5RewardCycle(pox) exposes the same lookup for an explicit is-pox-5-active check).
fetchProtocolBond is the read-only wrapper around the same data; fetchBond reads the map entry directly. Both return the same shape.
Quote required STX for a BTC commitment
Before enrolling, a partner computes how much STX must be paired with a given sats amount under the bond's static ratio.
Schedule helper
Convert between bond index, reward cycle, and burn height. Useful for "X days until next bond opens", re-lock window timing, and indexer pagination.
bondPhaseRanges returns the bond's lifecycle as four named, burn-height-anchored ranges — open, locked, unlocked, finished — each { name, startBurnHeight, length, endBurnHeight } with an exclusive endBurnHeight (the next phase begins there). It is PoxInfo-pure: no fetches. open ends prepareCycleLength blocks before the bond's start height, because registration is blocked in the final prepare phase (ERR_STAKE_IN_PREPARE_PHASE) — so open.endBurnHeight is the practical registration cutoff. unlocked is the last rewardCycleLength / 2 blocks of the term (the re-lock window), and finished is capped at one bond term's worth of blocks as a UI convention.
bondStatus classifies the current burn height (poxInfo.currentBurnchainBlockHeight) into one of those phase names without assuming the bond exists on-chain. For a bond where setup-bond hasn't been called (isBondSetup: false) it resolves to too-early (before the setup window), eligible (inside the BOND_GAP_CYCLES setup window — the admin can setup-bond now), or missed (the start height passed without setup; this bond period can never run). fetchBondStatus is the fetching variant — it fetches poxInfo and the setup check (fetchProtocolBond) for whatever isn't injected:
The same boundaries can be derived by hand with the pure conversions (all take { ..., poxInfo }, no network):
bondPeriodToRewardCycle/bondPeriodToBurnHeight— a bond's first reward cycle and start burn height.rewardCycleToBurnHeight— mirrorspox-5.reward-cycle-to-burn-height.burnHeightToRewardCycle— mirrorspox-5.burn-height-to-reward-cycle; throws whenburnHeightis belowfirst-burnchain-block-height, mirroring the contract's runtime abort.burnHeightToDistributionIndex— mirrorspox-5.burn-height-to-distribution-index; distribution cycles tick twice per reward cycle (everyrewardCycleLength / 2burn blocks).
The re-lock window at the end of every bond is when BTC unlocks before STX. On mainnet it is 1,050 blocks ≈ 7.3 days (pox-reward-cycle-length / 2, with pox-reward-cycle-length = 2100); on testnet it is 525. The contract's get-bond-l1-unlock-height(bondIndex) returns bondPeriodToBurnHeight(bondIndex + 6) - (pox-reward-cycle-length / 2); fetchBondL1UnlockHeight wraps it. This is the read to use for L1 timelock construction; the unlock-burn-height returned from register-for-bond / stake / stake-update is the start of the unlock cycle (a different value derived from reward-cycle-to-burn-height) and is only for indexing the membership's unlock schedule.
isInPreparePhase is the gate that register-for-bond, update-bond-registration, stake, stake-update, announce-l1-early-exit, and unstake-sbtc all hit (ERR_STAKE_IN_PREPARE_PHASE u47). unstake enforces the same window through its own separate check (ERR_UNSTAKE_IN_PREPARE_PHASE u28).
Earned rewards and protocol totals
The reward model splits "what is this signer owed?" into two raw on-chain reads, plus the get-earned aggregator that combines them (pox-5.clar:2341, pox-5.clar:3217, pox-5.clar:3203):
get-earned(signer, reward-cycle, bond-index) -> uint— pending + accrued, in sBTC sats. The single number a UI should display.get-signer-unclaimed-rewards-for-cycle— running pending sBTC for{ reward-cycle, bond-index, signer }since the last settlement (bond-index: (some N)for a bond cycle,nonefor STX-only).get-signer-rewards-per-token-settled-for-cycle— last-settled rewards-per-token snapshot for the same key.
Staker-level rewards
For a signer manager paying individual stakers out of its slice, the staker-level layer mirrors the signer-level reads one level down (pox-5.clar:2358, pox-5.clar:3247, pox-5.clar:3231, pox-5.clar:3263):
get-earned-staker-rewards(signer, reward-cycle, bond-index, staker) -> uint— pending + accrued for a single staker under one signer.get-staker-unclaimed-rewards-for-cycle— running pending sBTC for{ reward-cycle, bond-index, signer, staker }.get-staker-rewards-per-token-settled-for-cycle— staker's last-settled RPT snapshot.get-signer-rewards-per-token-for-cycle— the signer's own RPT watermark for the cycle, used as the staker-side RPT reference. Returns0nfor signers that never crossedSIGNER_SET_MIN_USTXin the cycle.
fetchStakerCustodiedSbtc returns the sBTC the contract currently holds for a given staker (the source side of roll-sbtc). fetchBondOverlapsNewPosition wraps the on-chain overlap predicate and is the right call to gate a rollover UI without broadcasting.
For protocol-wide totals:
Was this helpful?