Skip to content

feat(sdk-coin-stx): support post-conditions on contract calls - #9978

Merged
Doddanna17 merged 1 commit into
masterfrom
doddannab/si-1936-sdk-coin-stx-postconditions-register-for-bond
Oct 15, 2026
Merged

Doddanna17 merged 1 commit into
masterfrom
doddannab/si-1936-sdk-coin-stx-postconditions-register-for-bond

Conversation

@Doddanna17

@Doddanna17 Doddanna17 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Context

PoX-5 register-for-bond (TXN 2) moves the staker's STX (it locks amount-ustx). Wallet Platform built the contract call with no post-condition mode/conditions; @stacks/transactions defaults PostConditionMode to Deny, so the STX movement is "unchecked" and the tx aborts on-chain with abort_by_post_condition (SIP-005). SI-1936.

Change

  • modules/sdk-coin-stx/src/lib/abstractContractBuilder.ts:
    • add fluent setters postConditionMode(mode) and postConditions(postConditions);
    • initBuilder: restore _postConditionMode / _postConditions from the deserialized tx — toJson() doesn't carry them, so without this the fee re-build (buildTransactionWithFee → factory.from(hex)) would silently drop them.
  • modules/sdk-coin-stx/test/unit/transactionBuilder/pox5Builder.ts: assert the register-for-bond tx carries Deny + the STX post-condition, and that they survive the re-build.

Why the SDK

WP builds via factory.getContractBuilder(); the builder holds _postConditionMode/_postConditions but exposed no public setter, so WP couldn't set them. This adds the minimal surface.

Commits

SHA subject
654ef00e24 feat(sdk-coin-stx): support post-conditions on contract calls

Tests

  • pox5Builder unit test: register-for-bond with Deny + makeStandardSTXPostCondition(...) → built tx has the mode + 1 post-condition, and the re-built (fee) tx preserves them.

Verification

  • npx tsc -p tsconfig.json --noEmit → exit 0; eslint --quiet clean.
  • CI runs the sdk-coin-stx unit suite.

Review follow-ups

  • None yet.

Rationale — post-conditions (Deny vs Allow)

What "empty" means

"Empty" = no post-conditions; it's not a mode by itself — the outcome depends on the mode:

no post-conditions (empty) with a post-condition
Allow commits — contract moves freely commits if the PC holds
Deny aborts — any movement is "unchecked" (the bug) commits if the PC holds

Under Deny you must declare the movement; if you want empty you must use Allow. The real choice is Allow + empty (sandbox) vs Deny + post-condition (this work).

Why Deny + post-condition (impact by scenario)

register-for-bond moves the staker's 100,000,000 uSTX. Deny + makeStandardSTXPostCondition(staker, LessEqual, 100000000) = "the network must confirm the staker lost at most 100,000,000 STX — nothing else."

Scenario Allow + empty Deny + PC
A. Honest pox-5 register commits commits (PC holds)
B. Buggy/malicious/upgraded contract moves more commits → lose the extra aborts → funds safe
C. Wiring / mis-sign error commits → wrong movement aborts → caught before loss
D. Unexpected extra movement allowed aborts
E. Fee/refund changes the net unaffected must bound exactly, else aborts (safe)

Custody moves user funds → Deny + PC cryptographically constrains the tx to move at most the staked amount. Trade-off (E): the bound must be exact — whether the tx fee counts is the one thing to confirm on the wire (evidence says no; the node cited only the 100,000,000 stake).

Related

  • SI-1936 (this); SI-1903 (umbrella); SI-1922 / SI-1925 (prior register-leg fixes).
  • Wallet Platform PR (sets Deny + STX post-condition on the register build) follows once this publishes a @bitgo-beta/sdk-coin-stx beta.

Add fluent setters postConditionMode()/postConditions() to
AbstractContractBuilder, and restore them in initBuilder from the
deserialized tx so a re-build (the fee re-build via factory.from) does
not drop them.

The PoX-5 register-for-bond contract call moves the staker's STX; with
@stacks/transactions defaulting PostConditionMode to Deny and no
post-conditions declared, the tx aborts on-chain with
abort_by_post_condition. Wallet Platform now sets Deny + an STX
post-condition (SI-1936).

Ticket: SI-1936
@linear-code

linear-code Bot commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

SI-1936

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Unit tests are failing on Node 26.x (Current release line, non-blocking). This is not an LTS version yet, so it does not block merge, but it signals an incompatibility to fix before Node 26.x becomes LTS.

View run

@Doddanna17
Doddanna17 marked this pull request as ready for review October 15, 2026 11:15
@Doddanna17
Doddanna17 requested a review from a team as a code owner October 15, 2026 11:15
@Doddanna17
Doddanna17 merged commit baafc30 into master Oct 15, 2026
26 checks passed
@Doddanna17

Copy link
Copy Markdown
Contributor Author

Context (SI-1936, 2026-10-15): the register-for-bond (native BTC) post-condition must be the SIP-044 staking-postcondition ({ type: "staking-postcondition", address, condition: "eq", amount }, wire PostConditionType.Staking = 3}), NOT an STX post-condition. This setter change is still the right plumbing, but it currently only transports whatever the caller passes — and the caller (WP) builds it with @stacks/transactions 2.0.1, which has no Staking type. So the STX stack needs @stacks/transactions v7 (or a manual staking-PC builder) to actually produce the condition. Not blocking this PR, but flagging the type expectation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants