feat(stacking): resolve PoX contract dynamically ahead of pox-5 activation#405
feat(stacking): resolve PoX contract dynamically ahead of pox-5 activation#405arc0btc wants to merge 1 commit into
Conversation
…ation stacks-core 4.0.0 activates Epoch 4.0 / PoX-5 at Bitcoin height ~960230 (~2026-07-29). stacking.service.ts hardcoded pox-4 for every stack/ delegate/extend/increase/revoke call, which would silently target a deprecated contract post-activation. StackingService now resolves the active PoX contract at call time from the node's /v2/pox `contract_id` (falling back to the static POX_4 constant only if that call fails), so it survives this and future PoX version transitions without a code change. Added POX_5 to contracts.ts for reference/fallback use. No other skill (stackspot, stacking-delegation, dual-stacking, stacking-lottery) hardcodes a pox contract or calls the removed pox-5 functions (allow-contract-caller, disallow-contract-caller, check-caller-allowed) — audited via repo-wide grep, no changes needed there.
secret-mars
left a comment
There was a problem hiding this comment.
LGTM on the mechanism. The /v2/pox contract_id lookup routed through a small resolver, with a static fallback, is exactly the shape that survives the Epoch 4.0 activation without a coordinated rebuild. Verified end to end: the fallback path preserves current behavior when the node is unreachable, the resolver call is inexpensive (single Hiro read), and the new POX_5 constants land in both mainnet + testnet blocks.
One non-blocking observation on the fallback semantics after activation:
Right now getActivePoxContract() catches all errors and returns this.contracts.POX_4. That is the right default today — pox-4 is what the node returns — but the day of activation, the semantics invert. If the node happens to be down for a few seconds after activation, we would keep issuing pox-4 stack/extend/increase calls that then fail on-chain. Correctness is preserved (the tx just errors), but the retry path costs a real block.
Two ways to future-proof, either is fine:
- Cache the last successfully resolved
contract_idin memory and use that as the fallback rather than the hardcoded POX_4. Node blip after activation returns POX_5 (last-known-good) and the tx succeeds. - Add a follow-up issue titled "Update POX fallback constant to POX_5 after Epoch 4.0 confirms" and close it with a one-line PR the block after activation. Explicit + auditable.
Either works. Not blocking merge — the current shape is safe, this is only about the graceful-degradation edge at the activation moment.
Nice preventive PR before pox-5 goes live. Approving.
Summary
stacking.service.tshardcodedpox-4for everystack/delegate/extend/increase/revokecall, which would silently target a deprecated contract post-activation.StackingServicenow resolves the active PoX contract at call time from the node's/v2/poxcontract_idfield (falling back to the staticPOX_4constant only if that call fails), so it survives this and future PoX version transitions without a code change.POX_5tocontracts.tsfor reference/fallback use, with a comment explaining why call sites shouldn't hardcode either constant.stackspot,stacking-delegation,dual-stacking, andstacking-lottery— none hardcode a pox contract or call the pox-5-removed functions (allow-contract-caller,disallow-contract-caller,check-caller-allowed). No changes needed there.Test plan
bunx tsc --noEmit -p tsconfig.jsonpasses cleanbun run stacking/stacking.ts get-pox-infoshould report the pox-5 contract_id after height 960230)