
If you're working with Stacks and want to lock STX to earn Bitcoin rewards through Proof of Transfer, this handles the full stacking workflow. You get read-only queries for PoX cycle info and stacking status, plus write operations to lock tokens or extend an existing lock period. The Bitcoin address handling requires version bytes and hashbytes instead of standard base58 addresses, which is a bit low-level but gives you full control. Built by whoabuddy, requires an unlocked wallet for anything beyond queries. Useful if you're building stacking tooling or managing positions programmatically rather than clicking through a web interface.
npx -y skills add aibtcdev/skills --skill stacking --agent claude-codeInstalls into .claude/skills of the current project.
STX staking on PoX-5 (active on mainnet since reward cycle 141). PoX-5 is not a rename of pox-4:
signer-manager-trait. There is no pool delegation (delegate-stx) and no reward address on the stake itself; the manager decides who may join and reads any payout preferences from optional signer calldata.claim-rewards) and then pays the staker (claim-staker-rewards). Managers implement that second step differently, and some pay off-chain.stake, stake-update and unstake are refused during the prepare phase (the last 100 blocks of each cycle).Every write first confirms pox-5 is still the network's active PoX contract and refuses before signing otherwise.
bun run stacking/stacking.ts <subcommand> [options]
Current PoX-5 state.
bun run stacking/stacking.ts get-pox-info
Output:
{
"network": "mainnet",
"contract": "SP000000000000000000002Q6VF78.pox-5",
"burnHeight": 967284,
"rewardCycle": 143,
"nextCycleStartHeight": 968450,
"preparePhaseStartHeight": 968350,
"inPreparePhase": false,
"firstBurnHeight": 666050,
"rewardCycleLength": 2100,
"prepareCycleLength": 100,
"signerSetMinUstx": "50000000000",
"totalLiquidSupplyUstx": "1867006091924579"
}
signerSetMinUstx is the minimum a signer needs delegated to join the signer set, not a per-staker minimum.
bun run stacking/stacking.ts get-stacking-status [--address <addr>]
Output:
{
"address": "SP1PSZZYFH9H5M81KGBV2XTFQBR3HQ17HJW7Y4ZK2",
"network": "mainnet",
"staking": true,
"stake": {
"signerManager": "SP8HK160YD5GHXP69VGA0TC7AQJ1X4CDW3XVERSE.xverse-signer-manager-2",
"amountUstx": "500000000",
"amount": "500.000000 STX",
"firstRewardCycle": 144,
"numCycles": 96,
"unlockCycle": 240,
"unlockBurnHeight": 1170050
},
"bond": null,
"balance": {
"totalUstx": "560494462",
"lockedUstx": "500000000",
"unlockedUstx": "60494462",
"burnchainUnlockHeight": 1170050
},
"pox": { "contract": "SP000000000000000000002Q6VF78.pox-5", "burnHeight": 967284, "rewardCycle": 143, "nextCycleStartHeight": 968450, "preparePhaseStartHeight": 968350, "inPreparePhase": false }
}
bond is set when the address is in a pox-5 protocol bond (bondIndex, amountUstx, amountSats, isL1Lock, signerManager).
Signer managers in the signer set for a reward cycle (default: the next cycle, which a new stake joins), sorted by delegated STX.
bun run stacking/stacking.ts list-signers [--reward-cycle <cycle>] [--with-payout-info]
Options:
--reward-cycle (optional) — cycle to list--with-payout-info (optional) — also read each manager's staker-claim style: staker-arg / caller (on-chain claim works) or none (pays off-chain). One extra request per signer.Output (truncated):
{
"network": "mainnet",
"rewardCycle": 144,
"count": 26,
"signers": [
{ "signerManager": "SP1N8F8BBBC60XF6HJBNJHKPRGJ7WZBRGNDJX4YDR.signer-manager", "delegatedUstx": "85815090000000", "delegated": "85815090.000000 STX" }
],
"note": "Managers can restrict who may stake ..."
}
Only signers at or above the signer-set minimum appear. Other registered managers can still be staked with by contract id.
Lock STX with a signer manager. The lock starts next reward cycle.
bun run stacking/stacking.ts stack-stx \
--signer-manager <contractId> \
--amount <microStx> \
--num-cycles <1-96> \
[--btc-reward-address <address> [--max-withdrawal-fee-sats <sats>] | --signer-calldata-hex <hex>]
Options:
--signer-manager (required) — signer manager contract id (see list-signers)--amount (required) — micro-STX to lock (1 STX = 1,000,000 micro-STX)--num-cycles (required) — reward cycles to lock, 1–96 (one cycle ≈ 2 weeks)--btc-reward-address (optional) — Bitcoin address for rewards, encoded as { pox-addr, max-fee } calldata, the shape reference managers (e.g. Xverse, Fast Pool) use to pay via an sBTC withdrawal. Some managers require it, some ignore it.--max-withdrawal-fee-sats (optional) — max sBTC withdrawal fee per payout with --btc-reward-address (default 3000)--signer-calldata-hex (optional) — raw calldata (≤500 bytes) for managers with a custom format; not combinable with --btc-reward-addressRefused before signing when: in the prepare phase (or within 3 burn blocks of it), already staking (use extend-stacking), the manager is not a registered pox-5 signer, or the amount exceeds the address's STX balance.
Output:
{
"success": true,
"txid": "0x...",
"explorerUrl": "https://explorer.hiro.so/txid/0x...?chain=mainnet",
"staker": "SP...",
"signerManager": "SP8HK160YD5GHXP69VGA0TC7AQJ1X4CDW3XVERSE.xverse-signer-manager-2",
"amountUstx": "2780000000",
"amount": "2780.000000 STX",
"firstRewardCycle": 144,
"numCycles": 96,
"unlockCycle": 240,
"unlockBurnHeight": 1170050,
"rewardPayout": "sBTC (if the manager supports it)",
"network": "mainnet"
}
Update an existing stake (stake-update): extend, add STX, switch signer manager, or change payout calldata, in any combination.
bun run stacking/stacking.ts extend-stacking \
[--cycles-to-extend <cycles>] [--amount-increase <microStx>] [--signer-manager <contractId>] \
(--btc-reward-address <address> [--max-withdrawal-fee-sats <sats>] | --signer-calldata-hex <hex> | --sbtc-payout)
A payout choice is required on every update. pox-5 passes stake-update's calldata to the manager's validate-stake! each time, and reference managers (Xverse, Fast Pool) treat no calldata as "delete the stored BTC payout address"; others reject it. To keep a BTC payout, pass the same --btc-reward-address again. --sbtc-payout sends no calldata on purpose.
Refused when no payout choice is given, when not staking, in the prepare phase, nothing would change, the increase exceeds the unlocked balance, the new manager is not registered, or the lock would run more than 96 cycles past the next cycle.
Output: txid, explorerUrl, previous (the stake before), signerManager, newAmountUstx, newAmount, unlockCycle, unlockBurnHeight.
Stop a stake early (unstake). STX stays locked through the current cycle and unlocks at the start of the next.
bun run stacking/stacking.ts unstake-stx
Refused when not staking, in the prepare phase, or when the stake already unlocks next cycle.
Output: txid, explorerUrl, previous, unlockCycle, unlockBurnHeight.
sBTC claimable from a signer manager for one cycle. pox-5's get-earned-staker-rewards only catches up when the manager pulls the cycle's rewards, so this projects the amount the staker can claim after that pull (pox-5's own compute-earned-rewards over the cycle's global rewards-per-token).
bun run stacking/stacking.ts get-rewards --reward-cycle <cycle> [--address <addr>] [--signer-manager <contractId>]
Output:
{
"address": "SP...",
"network": "mainnet",
"rewardCycle": 143,
"signerManager": "SP8HK160YD5GHXP69VGA0TC7AQJ1X4CDW3XVERSE.xverse-signer-manager-2",
"unclaimedSatsBeforeFees": "0",
"managerUnpulledSats": "0",
"stakerClaim": "staker-arg",
"next": "Nothing to claim for this cycle."
}
Claim one cycle's sBTC rewards through the signer manager. If the manager has not pulled that cycle's rewards from pox-5 yet, this first sends the manager's permissionless claim-rewards (a second transaction paid by this wallet), then the staker claim.
bun run stacking/stacking.ts claim-rewards --reward-cycle <cycle> [--signer-manager <contractId>]
Refused when the manager has no on-chain staker claim or nothing is claimable (projected as in get-rewards, so rewards the manager has not pulled yet still count). When two transactions are sent they use consecutive explicit nonces. The sBTC post-conditions use the token pox-5 reports in /v2/pox (pox_5_sbtc_contract), which differs from the default sBTC on testnet. Post-conditions allow sBTC only out of pox-5 (the pull) and the manager (the payout); nothing may leave the caller.
Output: txid, explorerUrl, unclaimedSatsBeforeFees, managerPull ({ txid, explorerUrl } or null), and a note when two transactions were sent.
stake, stake-update and unstake transactions.bun run wallet/wallet.ts unlock).direct-stack-stx --pool fast-pool and direct-revoke-fast-pool go through pox-4 in the Pillar wallet contract and are refused while pox-4 is not active; use this skill for PoX-5 staking.